mirror of
https://github.com/anklimov/lighthub
synced 2025-12-06 19:59:50 +03:00
1181 lines
34 KiB
C++
1181 lines
34 KiB
C++
/* Copyright © 2017-2018 Andrey Klimov. All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
Homepage: http://lazyhome.ru
|
|
GIT: https://github.com/anklimov/lighthub
|
|
e-mail anklimov@gmail.com
|
|
|
|
*/
|
|
#include "options.h"
|
|
#include "item.h"
|
|
#include "aJSON.h"
|
|
#include "utils.h"
|
|
|
|
#ifdef _dmxout
|
|
|
|
#include "dmx.h"
|
|
#include "FastLED.h"
|
|
|
|
#endif
|
|
|
|
#include <ModbusMaster.h>
|
|
#include <PubSubClient.h>
|
|
|
|
|
|
short modbusBusy = 0;
|
|
extern aJsonObject *pollingItem;
|
|
|
|
|
|
//int modbusSet(int addr, uint16_t _reg, int _mask, uint16_t value);
|
|
|
|
extern PubSubClient mqttClient;
|
|
//extern char outprefix[];
|
|
const char outprefix[] PROGMEM = OUTTOPIC;
|
|
|
|
static unsigned long lastctrl = 0;
|
|
static aJsonObject *lastobj = NULL;
|
|
|
|
int txt2cmd(char *payload) {
|
|
int cmd = -1;
|
|
|
|
// Check for command
|
|
if (strcmp(payload, "ON") == 0) cmd = CMD_ON;
|
|
else if (strcmp(payload, "OFF") == 0) cmd = CMD_OFF;
|
|
else if (strcmp(payload, "REST") == 0) cmd = CMD_RESTORE;
|
|
else if (strcmp(payload, "TOGGLE") == 0) cmd = CMD_TOGGLE;
|
|
else if (strcmp(payload, "HALT") == 0) cmd = CMD_HALT;
|
|
else if (*payload == '-' || (*payload >= '0' && *payload <= '9')) cmd = 0;
|
|
else if (*payload == '{') cmd = -2;
|
|
else if (*payload == '#') cmd = -3;
|
|
|
|
return cmd;
|
|
}
|
|
|
|
const short defval[4] = {0, 0, 0, 0}; //Type,Arg,Val,Cmd
|
|
|
|
Item::Item(aJsonObject *obj)//Constructor
|
|
{
|
|
itemArr = obj;
|
|
Parse();
|
|
}
|
|
|
|
void Item::Parse() {
|
|
if (isValid()) {
|
|
// Todo - avoid static enlarge for every types
|
|
for (int i = aJson.getArraySize(itemArr); i < 4; i++)
|
|
aJson.addItemToArray(itemArr, aJson.createItem(
|
|
int(defval[i]))); //Enlarge item to 4 elements. VAL=int if no other definition in conf
|
|
|
|
|
|
itemType = aJson.getArrayItem(itemArr, I_TYPE)->valueint;
|
|
itemArg = aJson.getArrayItem(itemArr, I_ARG);
|
|
itemVal = aJson.getArrayItem(itemArr, I_VAL);
|
|
|
|
/*
|
|
Serial.print(F(" Item:"));
|
|
Serial.print(itemArr->name);
|
|
Serial.print(F(" T:"));
|
|
Serial.print(itemType);
|
|
Serial.print(F(" ="));
|
|
Serial.println(getArg());
|
|
*/
|
|
}
|
|
}
|
|
|
|
Item::Item(char *name) //Constructor
|
|
{
|
|
if (name)
|
|
itemArr = aJson.getObjectItem(items, name);
|
|
else itemArr = NULL;
|
|
|
|
Parse();
|
|
}
|
|
|
|
uint8_t Item::getCmd() {
|
|
aJsonObject *t = aJson.getArrayItem(itemArr, I_CMD);
|
|
if (t)
|
|
return t->valueint;
|
|
else return -1;
|
|
}
|
|
|
|
|
|
void Item::setCmd(uint8_t cmd) {
|
|
aJsonObject *t = aJson.getArrayItem(itemArr, I_CMD);
|
|
if (t)
|
|
t->valueint = cmd;
|
|
}
|
|
|
|
int Item::getArg(short n) //Return arg int or first array element if Arg is array
|
|
{
|
|
if (!itemArg) return -1;
|
|
if (itemArg->type == aJson_Int){
|
|
if (!n) return itemArg->valueint; else return -1;
|
|
}
|
|
if ((itemArg->type == aJson_Array) && ( n < aJson.getArraySize(itemArg))) return aJson.getArrayItem(itemArg, n)->valueint;
|
|
else return -2;
|
|
}
|
|
|
|
/*
|
|
int Item::getVal(short n) //Return Val from Value array
|
|
{ if (!itemVal) return -1;
|
|
else if (itemVal->type==aJson_Array)
|
|
{
|
|
aJsonObject *t = aJson.getArrayItem(itemVal,n);
|
|
if (t) return t->valueint;
|
|
else return -3;
|
|
}
|
|
else return -2;
|
|
}
|
|
*/
|
|
|
|
long int Item::getVal() //Return Val if val is int or first elem of Value array
|
|
{
|
|
if (!itemVal) return -1;
|
|
if (itemVal->type == aJson_Int) return itemVal->valueint;
|
|
else if (itemVal->type == aJson_Array) {
|
|
aJsonObject *t = aJson.getArrayItem(itemVal, 0);
|
|
if (t) return t->valueint;
|
|
else return -3;
|
|
} else return -2;
|
|
}
|
|
|
|
/*
|
|
void Item::setVal(short n, int par) // Only store if VAL is array defined in config to avoid waste of RAM
|
|
{
|
|
if (!itemVal || itemVal->type!=aJson_Array) return;
|
|
Serial.print(F(" Store p="));Serial.print(n);Serial.print(F(" Val="));Serial.println(par);
|
|
for (int i=aJson.getArraySize(itemVal);i<=n;i++) aJson.addItemToArray(itemVal,aJson.createItem(int(0))); //Enlarge array of Values
|
|
|
|
aJsonObject *t = aJson.getArrayItem(itemVal,n);
|
|
if (t) t->valueint=par;
|
|
}
|
|
*/
|
|
|
|
void Item::setVal(long int par) // Only store if VAL is int (autogenerated or config-defined)
|
|
{
|
|
if (!itemVal || itemVal->type != aJson_Int) return;
|
|
// Serial.print(F(" Store "));
|
|
// Serial.print(F(" Val="));
|
|
// Serial.println(par);
|
|
itemVal->valueint = par;
|
|
}
|
|
|
|
|
|
boolean Item::isValid() {
|
|
return (itemArr && (itemArr->type == aJson_Array));
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
void Item::copyPar (aJsonObject *itemV)
|
|
{ int n=aJson.getArraySize(itemV);
|
|
//for (int i=aJson.getArraySize(itemVal);i<n;i++) aJson.addItemToArray(itemVal,aJson.createItem(int(0))); //Enlarge array of Values
|
|
for (int i=0;i<n;i++) setPar(i,aJson.getArrayItem(itemV,i)->valueint);
|
|
}
|
|
*/
|
|
|
|
#if defined(ARDUINO_ARCH_ESP32)
|
|
void analogWrite(int pin, int val)
|
|
{
|
|
//TBD
|
|
}
|
|
#endif
|
|
|
|
|
|
boolean Item::getEnableCMD(int delta) {
|
|
return ((millis() - lastctrl > (unsigned long) delta));// || ((void *)itemArr != (void *) lastobj));
|
|
}
|
|
|
|
#define MAXCTRLPAR 3
|
|
|
|
|
|
int Item::Ctrl(char * payload, boolean send){
|
|
int cmd = txt2cmd(payload);
|
|
switch (cmd) {
|
|
case 0: {
|
|
short i = 0;
|
|
int Par[3];
|
|
|
|
while (payload && i < 3)
|
|
Par[i++] = getInt((char **) &payload);
|
|
|
|
Ctrl(0, i, Par, send);
|
|
}
|
|
break;
|
|
|
|
case -1: //Not known command
|
|
case -2: //JSON input (not implemented yet
|
|
break;
|
|
#if not defined(ARDUINO_ARCH_ESP32) and not defined(ESP8266) and not defined(ARDUINO_ARCH_STM32F1)
|
|
case -3: //RGB color in #RRGGBB notation
|
|
{
|
|
CRGB rgb;
|
|
if (sscanf((const char*)payload, "#%2X%2X%2X", &rgb.r, &rgb.g, &rgb.b) == 3) {
|
|
int Par[3];
|
|
CHSV hsv = rgb2hsv_approximate(rgb);
|
|
Par[0] = map(hsv.h, 0, 255, 0, 365);
|
|
Par[1] = map(hsv.s, 0, 255, 0, 100);
|
|
Par[2] = map(hsv.v, 0, 255, 0, 100);
|
|
Ctrl(0, 3, Par, send);
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
case CMD_ON:
|
|
|
|
// if (item.getEnableCMD(500) || lanStatus == 4)
|
|
Ctrl(cmd, 0, NULL,
|
|
send); //Accept ON command not earlier then 500 ms after set settings (Homekit hack)
|
|
// else Serial.println(F("on Skipped"));
|
|
|
|
break;
|
|
default: //some known command
|
|
Ctrl(cmd, 0, NULL, send);
|
|
|
|
} //ctrl
|
|
}
|
|
|
|
|
|
int Item::Ctrl(short cmd, short n, int *Parameters, boolean send) {
|
|
|
|
|
|
Serial.print(F("Cmd="));Serial.print(cmd);Serial.print(F(" MEM="));Serial.println(freeRam());
|
|
|
|
int Par[MAXCTRLPAR] = {0, 0, 0};
|
|
if (Parameters)
|
|
for (short i=0;i<n && i<MAXCTRLPAR;i++) Par[i] = Parameters[i];
|
|
|
|
int iaddr = getArg();
|
|
HSVstore st;
|
|
switch (cmd) {
|
|
int t;
|
|
case CMD_TOGGLE:
|
|
if (isActive()) cmd = CMD_OFF;
|
|
else cmd = CMD_ON;
|
|
break;
|
|
|
|
case CMD_RESTORE:
|
|
if (itemType != CH_GROUP) //individual threating of channels. Ignore restore command for groups
|
|
switch (t = getCmd()) {
|
|
case CMD_HALT: //previous command was HALT ?
|
|
Serial.print(F("Restored from:"));
|
|
Serial.println(t);
|
|
cmd = CMD_ON; //turning on
|
|
break;
|
|
default:
|
|
return -3;
|
|
}//switch old cmd
|
|
} //switch cmd
|
|
|
|
switch (cmd) {
|
|
case 0: //no command
|
|
|
|
setCmd(CMD_SET);
|
|
|
|
switch (itemType) {
|
|
|
|
case CH_RGBW: //only if configured VAL array
|
|
if (!Par[1]) itemType = CH_WHITE;
|
|
case CH_GROUP: //Save for groups as well
|
|
case CH_RGB:
|
|
st.h = Par[0];
|
|
st.s = Par[1];
|
|
st.v = Par[2];
|
|
setVal(st.aslong);
|
|
if (send) SendStatus(0,3,Par,true); // Send back triplet ?
|
|
break;
|
|
|
|
case CH_PWM:
|
|
case CH_VC:
|
|
case CH_DIMMER:
|
|
case CH_MODBUS:
|
|
case CH_VCTEMP:
|
|
if (send) SendStatus(0, 1, Par,true); // Send back parameter for channel above this line
|
|
case CH_THERMO:
|
|
setVal(Par[0]); // Store value
|
|
|
|
}//itemtype
|
|
|
|
lastctrl = millis(); //last controlled object ond timestamp update
|
|
lastobj = itemArr;
|
|
|
|
break;
|
|
|
|
case CMD_ON:
|
|
if (itemType==CH_RGBW && getCmd() == CMD_ON && getEnableCMD(500)) {
|
|
Serial.println(F("Force White"));
|
|
itemType = CH_WHITE;
|
|
Par[1] = 0; //Zero saturation
|
|
Par[2] = 100; //Full power
|
|
// Store
|
|
st.h = Par[0];
|
|
st.s = Par[1];
|
|
st.v = Par[2];
|
|
setVal(st.aslong);
|
|
//Send to OH
|
|
if (send) SendStatus(0, 3, Par);
|
|
break;
|
|
} // if forcewhite
|
|
|
|
// if (itemType!=CH_RGBW || getCmd() != CMD_ON) {
|
|
{
|
|
short params = 0;
|
|
setCmd(cmd);
|
|
//retrive stored values
|
|
st.aslong = getVal();
|
|
|
|
// If command is ON but saved volume to low - setup mimimum volume
|
|
switch (itemType) {
|
|
case CH_DIMMER:
|
|
case CH_MODBUS:
|
|
if (st.aslong<MIN_VOLUME) st.aslong=INIT_VOLUME;
|
|
setVal(st.aslong);
|
|
break;
|
|
case CH_RGB:
|
|
case CH_RGBW:
|
|
if (st.aslong && (st.v<MIN_VOLUME)) st.v=INIT_VOLUME;
|
|
setVal(st.aslong);
|
|
}
|
|
|
|
if (st.aslong > 0) //Stored smthng
|
|
|
|
switch (itemType) {
|
|
//case CH_GROUP:
|
|
case CH_RGBW:
|
|
case CH_RGB:
|
|
Par[0] = st.h;
|
|
Par[1] = st.s;
|
|
Par[2] = st.v;
|
|
params = 3;
|
|
SendStatus(0, params, Par,true); // Send restored triplet. In any cases
|
|
break;
|
|
|
|
case CH_VCTEMP:
|
|
case CH_PWM:
|
|
case CH_DIMMER: //Everywhere, in flat VAL
|
|
case CH_MODBUS:
|
|
case CH_VC:
|
|
Par[0] = st.aslong;
|
|
params = 1;
|
|
SendStatus(0, params, Par, true); // Send restored parameter, even if send=false - no problem, loop will be supressed at next hop
|
|
break;
|
|
case CH_THERMO:
|
|
Par[0] = st.aslong;
|
|
params = 0;
|
|
if (send) SendStatus(CMD_ON); // Just ON (switch)
|
|
break;
|
|
default:
|
|
if (send) SendStatus(cmd); // Just send ON
|
|
}//itemtype switch
|
|
else {// Default settings, values not stored yet
|
|
Serial.print(st.aslong);
|
|
Serial.println(F(": No stored values - default"));
|
|
|
|
switch (itemType) {
|
|
case CH_VCTEMP:
|
|
case CH_THERMO:
|
|
Par[0] = 20; //20 degrees celsium - safe temperature
|
|
params = 1;
|
|
SendStatus(0, params, Par);
|
|
break;
|
|
case CH_RGBW:
|
|
case CH_RGB:
|
|
Par[0] = 100;
|
|
Par[1] = 0;
|
|
Par[2] = 100;
|
|
params = 3;
|
|
SendStatus(0, params, Par,true);
|
|
break;
|
|
case CH_RELAY:
|
|
Par[0] = 100;
|
|
params = 1;
|
|
if (send) SendStatus(CMD_ON);
|
|
break;
|
|
default:
|
|
Par[0] = 100;
|
|
params = 1;
|
|
SendStatus(0, params, Par);
|
|
}
|
|
} // default handler
|
|
for (short i = 0; i < params; i++) {
|
|
Serial.print(F("Restored: "));
|
|
Serial.print(i);
|
|
Serial.print(F("="));
|
|
Serial.println(Par[i]);
|
|
}
|
|
/*
|
|
} else { //Double ON - apply special preset - clean white full power
|
|
if (getEnableCMD(500)) switch (itemType) {
|
|
case CH_RGBW:
|
|
Serial.println(F("Force White"));
|
|
itemType = CH_WHITE;
|
|
Par[1] = 0; //Zero saturation
|
|
Par[2] = 100; //Full power
|
|
|
|
// Store
|
|
st.h = Par[0];
|
|
st.s = Par[1];
|
|
st.v = Par[2];
|
|
setVal(st.aslong);
|
|
|
|
//Send to OH
|
|
if (send) SendStatus(0, 3, Par);
|
|
break;
|
|
} //itemtype
|
|
} //else
|
|
*/
|
|
//Serial.print("Sa:");Serial.println(Par[1]);
|
|
if ((itemType == CH_RGBW) && (Par[1] == 0)) itemType = CH_WHITE;
|
|
}
|
|
|
|
break; //CMD_ON
|
|
|
|
case CMD_OFF:
|
|
if (getCmd() != CMD_HALT) //if channels are halted - OFF to be ignored (restore issue)
|
|
{
|
|
Par[0] = 0;
|
|
Par[1] = 0;
|
|
Par[2] = 0;
|
|
setCmd(cmd);
|
|
if (send) SendStatus(cmd);
|
|
}
|
|
break;
|
|
|
|
case CMD_HALT:
|
|
|
|
if (isActive() > 0) {
|
|
Par[0] = 0;
|
|
Par[1] = 0;
|
|
Par[2] = 0;
|
|
setCmd(cmd);
|
|
SendStatus(CMD_OFF); //HALT to OFF mapping - send in any cases
|
|
Serial.println(F(" Halted"));
|
|
}
|
|
|
|
|
|
}//switch cmd
|
|
|
|
|
|
|
|
|
|
switch (itemType) {
|
|
|
|
#ifdef _dmxout
|
|
case CH_DIMMER: //Dimmed light
|
|
|
|
DmxWrite(iaddr, map(Par[0], 0, 100, 0, 255));
|
|
|
|
break;
|
|
|
|
|
|
case CH_RGBW: //Colour RGBW
|
|
{
|
|
int k;
|
|
DmxWrite(iaddr + 3, k = map((100 - Par[1]) * Par[2], 0, 10000, 0, 255));
|
|
Serial.print(F("W:"));
|
|
Serial.println(k);
|
|
}
|
|
|
|
|
|
case CH_RGB: // RGB
|
|
|
|
{
|
|
|
|
|
|
CRGB rgb = CHSV(map(Par[0], 0, 365, 0, 255), map(Par[1], 0, 100, 0, 255), map(Par[2], 0, 100, 0, 255));
|
|
|
|
DmxWrite(iaddr, rgb.r);
|
|
DmxWrite(iaddr + 1, rgb.g);
|
|
DmxWrite(iaddr + 2, rgb.b);
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
case CH_WHITE:
|
|
DmxWrite(iaddr, 0);
|
|
DmxWrite(iaddr + 1, 0);
|
|
DmxWrite(iaddr + 2, 0);
|
|
DmxWrite(iaddr + 3, map(Par[2], 0, 100, 0, 255));
|
|
break;
|
|
#endif
|
|
|
|
#ifdef _modbus
|
|
case CH_MODBUS: {
|
|
short numpar=0;
|
|
if ((itemArg->type == aJson_Array) && ((numpar = aJson.getArraySize(itemArg)) >= 2)) {
|
|
int _addr = aJson.getArrayItem(itemArg, 0)->valueint;
|
|
int _reg = aJson.getArrayItem(itemArg, 1)->valueint;
|
|
int _mask = -1;
|
|
if (numpar >= 3) _mask = aJson.getArrayItem(itemArg, 2)->valueint;
|
|
int _maxval = 0x3f;
|
|
if (numpar >=4) _maxval = aJson.getArrayItem(itemArg, 3)->valueint;
|
|
if (_maxval) modbusDimmerSet(_addr, _reg, _mask, map(Par[0], 0, 100, 0, _maxval));
|
|
else modbusDimmerSet(_addr, _reg, _mask, Par[0]);
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case CH_GROUP://Group
|
|
{
|
|
//aJsonObject *groupArr= aJson.getArrayItem(itemArr, 1);
|
|
if (itemArg->type == aJson_Array) {
|
|
aJsonObject *i = itemArg->child;
|
|
while (i) {
|
|
Item it(i->valuestring);
|
|
// it.copyPar(itemVal);
|
|
it.Ctrl(cmd, n, Par, send); //// was true
|
|
i = i->next;
|
|
} //while
|
|
} //if
|
|
} //case
|
|
break;
|
|
case CH_RELAY: {
|
|
int k;
|
|
pinMode(iaddr, OUTPUT);
|
|
digitalWrite(iaddr, k = ((cmd == CMD_ON) ? HIGH : LOW));
|
|
Serial.print(F("Pin:"));
|
|
Serial.print(iaddr);
|
|
Serial.print(F("="));
|
|
Serial.println(k);
|
|
break;
|
|
case CH_THERMO:
|
|
///thermoSet(name,cmd,Par1); all cativities done - update temp & cmd
|
|
break;
|
|
}
|
|
case CH_PWM: {
|
|
int k;
|
|
short inverse = 0;
|
|
if (iaddr < 0) {
|
|
iaddr = -iaddr;
|
|
inverse = 1;
|
|
}
|
|
pinMode(iaddr, OUTPUT);
|
|
//timer 0 for pin 13 and 4
|
|
//timer 1 for pin 12 and 11
|
|
//timer 2 for pin 10 and 9
|
|
//timer 3 for pin 5 and 3 and 2
|
|
//timer 4 for pin 8 and 7 and 6
|
|
//prescaler = 1 ---> PWM frequency is 31000 Hz
|
|
//prescaler = 2 ---> PWM frequency is 4000 Hz
|
|
//prescaler = 3 ---> PWM frequency is 490 Hz (default value)
|
|
//prescaler = 4 ---> PWM frequency is 120 Hz
|
|
//prescaler = 5 ---> PWM frequency is 30 Hz
|
|
//prescaler = 6 ---> PWM frequency is <20 Hz
|
|
int tval = 7; // this is 111 in binary and is used as an eraser
|
|
#if defined(__AVR_ATmega2560__)
|
|
TCCR4B &= ~tval; // this operation (AND plus NOT), set the three bits in TCCR2B to 0
|
|
TCCR3B &= ~tval;
|
|
tval = 2;
|
|
TCCR4B |= tval;
|
|
TCCR3B |= tval;
|
|
#endif
|
|
if (inverse) k = map(Par[0], 100, 0, 0, 255);
|
|
else k = map(Par[0], 0, 100, 0, 255);
|
|
analogWrite(iaddr, k);
|
|
Serial.print(F("Pin:"));
|
|
Serial.print(iaddr);
|
|
Serial.print(F("="));
|
|
Serial.println(k);
|
|
break;
|
|
}
|
|
#ifdef _modbus
|
|
case CH_VC:
|
|
|
|
VacomSetFan(Par[0], cmd);
|
|
break;
|
|
|
|
|
|
case CH_VCTEMP: {
|
|
Item it(itemArg->valuestring);
|
|
if (it.isValid() && it.itemType == CH_VC)
|
|
VacomSetHeat(it.getArg(), Par[0], cmd);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
} // switch itemtype
|
|
// break;
|
|
|
|
|
|
|
|
}
|
|
|
|
int Item::isActive() {
|
|
HSVstore st;
|
|
int val = 0;
|
|
|
|
if (!isValid()) return -1;
|
|
//Serial.print(itemArr->name);
|
|
|
|
int cmd = getCmd();
|
|
|
|
|
|
if (itemType != CH_GROUP)
|
|
// Simple check last command first
|
|
switch (cmd) {
|
|
case CMD_ON:
|
|
//Serial.println(" active");
|
|
return 1;
|
|
case CMD_OFF:
|
|
case CMD_HALT:
|
|
case -1: ///// No last command
|
|
//Serial.println(" inactive");
|
|
return 0;
|
|
}
|
|
|
|
// Last time was not a command but parameters set. Looking inside
|
|
st.aslong = getVal();
|
|
|
|
switch (itemType) {
|
|
case CH_GROUP: //make recursive calculation - is it some active in group
|
|
if (itemArg->type == aJson_Array) {
|
|
Serial.println(F("Grp check: "));
|
|
aJsonObject *i = itemArg->child;
|
|
while (i) {
|
|
Item it(i->valuestring);
|
|
|
|
if (it.isValid() && it.isActive()) {
|
|
Serial.println(F("Active"));
|
|
return 1;
|
|
}
|
|
i = i->next;
|
|
} //while
|
|
return 0;
|
|
} //if
|
|
break;
|
|
|
|
|
|
case CH_RGBW:
|
|
case CH_RGB:
|
|
|
|
|
|
val = st.v; //Light volume
|
|
break;
|
|
|
|
case CH_DIMMER: //Everywhere, in flat VAL
|
|
case CH_MODBUS:
|
|
case CH_THERMO:
|
|
case CH_VC:
|
|
case CH_VCTEMP:
|
|
case CH_PWM:
|
|
val = st.aslong;
|
|
} //switch
|
|
//Serial.print(F(":="));
|
|
//Serial.println(val);
|
|
if (val) return 1; else return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
short thermoSet(char * name, short cmd, short t)
|
|
{
|
|
|
|
if (items)
|
|
{
|
|
aJsonObject *item= aJson.getObjectItem(items, name);
|
|
if (item && (item->type==aJson_Array) && (aJson.getArrayItem(item, I_TYPE)->valueint==CH_THERMO))
|
|
{
|
|
|
|
for (int i=aJson.getArraySize(item);i<4;i++) aJson.addItemToArray(item,aJson.createItem(int(0))); //Enlarge item to 4 elements
|
|
if (!cmd) aJson.getArrayItem(item, I_VAL)->valueint=t;
|
|
aJson.getArrayItem(item, I_CMD)->valueint=cmd;
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void PooledItem::Idle()
|
|
{
|
|
if (PoolingInterval)
|
|
{
|
|
Pool();
|
|
next=millis()+PoolingInterval;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
addr 10d
|
|
Снять аварию 42001 (2001=7d1) =>4
|
|
|
|
[22:20:33] Write task has completed successfully
|
|
[22:20:33] <= Response: 0A 06 07 D0 00 04 89 FF
|
|
[22:20:32] => Poll: 0A 06 07 D0 00 04 89 FF
|
|
|
|
100%
|
|
2003-> 10000
|
|
[22:24:05] Write task has completed successfully
|
|
[22:24:05] <= Response: 0A 06 07 D2 27 10 33 C0
|
|
[22:24:05] => Poll: 0A 06 07 D2 27 10 33 C0
|
|
|
|
ON
|
|
2001->1
|
|
[22:24:50] Write task has completed successfully
|
|
[22:24:50] <= Response: 0A 06 07 D0 00 01 49 FC
|
|
[22:24:50] => Poll: 0A 06 07 D0 00 01 49 FC
|
|
|
|
OFF
|
|
2001->0
|
|
[22:25:35] Write task has completed successfully
|
|
[22:25:35] <= Response: 0A 06 07 D0 00 00 88 3C
|
|
[22:25:34] => Poll: 0A 06 07 D0 00 00 88 3C
|
|
|
|
|
|
POLL 2101x10
|
|
[22:27:29] <= Response: 0A 03 14 00 23 00 00 27 10 13 88 0B 9C 00 32 00 F8 00 F2 06 FA 01 3F AD D0
|
|
[22:27:29] => poll: 0A 03 08 34 00 0A 87 18
|
|
|
|
*/
|
|
|
|
void Item::mb_fail(short addr, short op, int val, int cmd) {
|
|
Serial.println(F("Modbus op failed"));
|
|
// int cmd = getCmd();
|
|
// if (cmd<0) return;
|
|
setCmd(cmd | CMD_RETRY);
|
|
setVal(val);
|
|
}
|
|
|
|
#ifndef MODBUS_DISABLE
|
|
extern ModbusMaster node;
|
|
|
|
|
|
|
|
int Item::VacomSetFan(int8_t val, int8_t cmd) {
|
|
int addr = getArg();
|
|
Serial.print(F("VC#"));
|
|
Serial.print(addr);
|
|
Serial.print(F("="));
|
|
Serial.println(val);
|
|
|
|
|
|
if (modbusBusy) {
|
|
mb_fail(1, addr, val, cmd);
|
|
return -1;
|
|
}
|
|
modbusBusy = 1;
|
|
|
|
uint8_t j;//, result;
|
|
//uint16_t data[1];
|
|
|
|
modbusSerial.begin(9600, fmPar);
|
|
node.begin(addr, modbusSerial);
|
|
|
|
if (val) {
|
|
node.writeSingleRegister(2001 - 1, 4 + 1);//delay(500);
|
|
//node.writeSingleRegister(2001-1,1);
|
|
} else node.writeSingleRegister(2001 - 1, 0);
|
|
delay(50);
|
|
node.writeSingleRegister(2003 - 1, val * 100);
|
|
|
|
modbusBusy = 0;
|
|
|
|
}
|
|
|
|
#define a 0.1842
|
|
#define b -36.68
|
|
|
|
int Item::VacomSetHeat(int addr, int8_t val, int8_t cmd) {
|
|
|
|
Serial.print(F("VC_heat#"));
|
|
Serial.print(addr);
|
|
Serial.print(F("="));
|
|
Serial.print(val);
|
|
Serial.print(F(" cmd="));
|
|
Serial.println(cmd);
|
|
if (modbusBusy) {
|
|
mb_fail(2, addr, val, cmd);
|
|
return -1;
|
|
}
|
|
modbusBusy = 1;
|
|
|
|
modbusSerial.begin(9600, fmPar);
|
|
node.begin(addr, modbusSerial);
|
|
|
|
uint16_t regval;
|
|
|
|
switch (cmd) {
|
|
case CMD_OFF:
|
|
case CMD_HALT:
|
|
regval = 0;
|
|
break;
|
|
|
|
default:
|
|
regval = round(((float) val - b) * 10 / a);
|
|
}
|
|
|
|
//Serial.println(regval);
|
|
node.writeSingleRegister(2004 - 1, regval);
|
|
modbusBusy = 0;
|
|
}
|
|
|
|
int Item::modbusDimmerSet(int addr, uint16_t _reg, int _mask, uint16_t value) {
|
|
|
|
if (modbusBusy) {
|
|
mb_fail(3, addr, value, 0);
|
|
|
|
return -1;
|
|
};
|
|
modbusBusy = 1;
|
|
|
|
modbusSerial.begin(9600, dimPar);
|
|
node.begin(addr, modbusSerial);
|
|
|
|
|
|
switch (_mask) {
|
|
case 1:
|
|
value <<= 8;
|
|
value |= (0xff);
|
|
break;
|
|
case 0:
|
|
value &= 0xff;
|
|
value |= (0xff00);
|
|
}
|
|
|
|
Serial.print(addr);
|
|
Serial.print(F("=>"));
|
|
Serial.print(_reg, HEX);
|
|
Serial.print(F(":"));
|
|
Serial.println(value, HEX);
|
|
|
|
node.writeSingleRegister(_reg, value);
|
|
modbusBusy = 0;
|
|
}
|
|
|
|
int Item::checkFM() {
|
|
if (modbusBusy) return -1;
|
|
if (checkModbusRetry()) return -2;
|
|
modbusBusy = 1;
|
|
|
|
uint8_t j, result;
|
|
int16_t data;
|
|
|
|
|
|
aJsonObject *out = aJson.createObject();
|
|
char *outch;
|
|
char addrstr[32];
|
|
|
|
strcpy_P(addrstr, outprefix);
|
|
strncat(addrstr, itemArr->name, sizeof(addrstr) - 1);
|
|
strncat(addrstr, "_stat", sizeof(addrstr) - 1);
|
|
|
|
// aJson.addStringToObject(out,"type", "rect");
|
|
|
|
|
|
modbusSerial.begin(9600, fmPar);
|
|
node.begin(getArg(), modbusSerial);
|
|
|
|
|
|
result = node.readHoldingRegisters(2101 - 1, 10);
|
|
|
|
// do something with data if read is successful
|
|
if (result == node.ku8MBSuccess) {
|
|
Serial.print(F(" FM Val :"));
|
|
for (j = 0; j < 10; j++) {
|
|
data = node.getResponseBuffer(j);
|
|
Serial.print(data, HEX);
|
|
Serial.print(F("-"));
|
|
|
|
}
|
|
int RPM;
|
|
// aJson.addNumberToObject(out,"gsw", (int) node.getResponseBuffer(1));
|
|
aJson.addNumberToObject(out, "V", (int) node.getResponseBuffer(2) / 100.);
|
|
// aJson.addNumberToObject(out,"f", (int) node.getResponseBuffer(3)/100.);
|
|
aJson.addNumberToObject(out, "RPM", RPM=(int) node.getResponseBuffer(4));
|
|
aJson.addNumberToObject(out, "I", (int) node.getResponseBuffer(5) / 100.);
|
|
aJson.addNumberToObject(out, "M", (int) node.getResponseBuffer(6) / 10.);
|
|
// aJson.addNumberToObject(out,"P", (int) node.getResponseBuffer(7)/10.);
|
|
// aJson.addNumberToObject(out,"U", (int) node.getResponseBuffer(8)/10.);
|
|
// aJson.addNumberToObject(out,"Ui", (int) node.getResponseBuffer(9));
|
|
aJson.addNumberToObject(out, "sw", (int) node.getResponseBuffer(0));
|
|
|
|
if (RPM && itemArg->type == aJson_Array)
|
|
{ aJsonObject *airGateObj = aJson.getArrayItem(itemArg, 1);
|
|
if (airGateObj) {
|
|
int val = 100;
|
|
Item item(airGateObj->valuestring);
|
|
if (item.isValid()) item.Ctrl(0,1,&val);
|
|
}
|
|
}
|
|
Serial.println();
|
|
} else {
|
|
Serial.print(F("Modbus polling error="));
|
|
Serial.println(result, HEX);
|
|
}
|
|
|
|
if (node.getResponseBuffer(0) & 8) //Active fault
|
|
{
|
|
result = node.readHoldingRegisters(2111 - 1, 1);
|
|
if (result == node.ku8MBSuccess) aJson.addNumberToObject(out, "flt", (int) node.getResponseBuffer(0));
|
|
} else aJson.addNumberToObject(out, "flt", 0);
|
|
|
|
delay(50);
|
|
result = node.readHoldingRegisters(20 - 1, 4);
|
|
|
|
// do something with data if read is successful
|
|
if (result == node.ku8MBSuccess) {
|
|
Serial.print(F(" PI Val :"));
|
|
for (j = 0; j < 4; j++) {
|
|
data = node.getResponseBuffer(j);
|
|
Serial.print(data);
|
|
Serial.print(F("-"));
|
|
|
|
}
|
|
|
|
int set = node.getResponseBuffer(0);
|
|
float fset;
|
|
float ftemp;
|
|
if (set) aJson.addNumberToObject(out, "set", fset =set * a + b);
|
|
aJson.addNumberToObject(out, "t", ftemp =(int) node.getResponseBuffer(1) * a + b);
|
|
// aJson.addNumberToObject(out,"d", (int) node.getResponseBuffer(2)*a+b);
|
|
int pwr = node.getResponseBuffer(3);
|
|
if (pwr > 0) aJson.addNumberToObject(out, "pwr", pwr / 10.); else aJson.addNumberToObject(out, "pwr", 0);
|
|
|
|
if (ftemp>FM_OVERHEAT_CELSIUS && set)
|
|
{
|
|
mqttClient.publish("/alarm/ovrht", itemArr->name);
|
|
Ctrl(CMD_OFF); //Shut down
|
|
}
|
|
Serial.println();
|
|
} else {
|
|
Serial.print(F("Modbus polling error="));
|
|
Serial.println(result, HEX);
|
|
}
|
|
|
|
|
|
outch = aJson.print(out);
|
|
mqttClient.publish(addrstr, outch);
|
|
free(outch);
|
|
aJson.deleteItem(out);
|
|
|
|
modbusBusy = 0;
|
|
}
|
|
|
|
boolean Item::checkModbusRetry() {
|
|
int cmd = getCmd();
|
|
if (cmd & CMD_RETRY) { // if last sending attempt of command was failed
|
|
int val = getVal();
|
|
Serial.println(F("Retrying CMD"));
|
|
cmd &= ~CMD_RETRY; // Clean retry flag
|
|
Ctrl(cmd,1,&val); // Execute command again
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int Item::checkModbusDimmer() {
|
|
if (modbusBusy) return -1;
|
|
if (checkModbusRetry()) return -2;
|
|
modbusBusy = 1;
|
|
|
|
uint8_t result;
|
|
|
|
uint16_t addr = getArg(0);
|
|
uint16_t reg = getArg(1);
|
|
// short mask = getArg(2);
|
|
|
|
int data;
|
|
|
|
//node.setSlave(addr);
|
|
|
|
modbusSerial.begin(9600, dimPar);
|
|
node.begin(addr, modbusSerial);
|
|
|
|
|
|
result = node.readHoldingRegisters(reg, 1);
|
|
|
|
if (result == node.ku8MBSuccess) {
|
|
data = node.getResponseBuffer(0);
|
|
Serial.print(F("MB: "));
|
|
Serial.print(itemArr->name);
|
|
Serial.print(F(" Val: "));
|
|
Serial.println(data, HEX);
|
|
checkModbusDimmer(data);
|
|
} else {
|
|
Serial.print(F("Modbus polling error="));
|
|
Serial.println(result, HEX);
|
|
}
|
|
|
|
modbusBusy = 0;
|
|
|
|
// Looking 1 step ahead for modbus item, which uses same register
|
|
Item nextItem(pollingItem->next);
|
|
if (pollingItem && nextItem.isValid() && nextItem.itemType == CH_MODBUS && nextItem.getArg(0) == addr &&
|
|
nextItem.getArg(1) == reg) {
|
|
nextItem.checkModbusDimmer(data);
|
|
pollingItem = pollingItem->next;
|
|
if (!pollingItem) pollingItem = items->child;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
int Item::checkModbusDimmer(int data) {
|
|
short mask = getArg(2);
|
|
if (mask < 0) return 0;
|
|
|
|
short maxVal = getArg(3);
|
|
if (maxVal<=0) maxVal = 0x3f;
|
|
|
|
int d = data;
|
|
if (mask == 1) d >>= 8;
|
|
if (mask == 0 || mask == 1) d &= 0xff;
|
|
|
|
if (maxVal) d = map(d, 0, maxVal, 0, 100);
|
|
|
|
int cmd = getCmd();
|
|
//Serial.println(d);
|
|
if (getVal() != d || d && cmd == CMD_OFF || d && cmd == CMD_HALT) //volume changed or turned on manualy
|
|
{
|
|
if (d) { // Actually turned on
|
|
if (cmd == CMD_OFF || cmd == CMD_HALT) SendStatus(CMD_ON); //update OH with ON if it was turned off before
|
|
SendStatus(0, 1, &d); //update OH with value
|
|
setCmd(CMD_ON); //store command
|
|
setVal(d); //store value
|
|
} else {
|
|
if (cmd != CMD_HALT && cmd != CMD_OFF) {
|
|
setCmd(CMD_OFF); // store command (not value)
|
|
SendStatus(CMD_OFF);// update OH
|
|
}
|
|
}
|
|
} //if data changed
|
|
}
|
|
|
|
int Item::Poll() {
|
|
switch (itemType) {
|
|
case CH_MODBUS:
|
|
checkModbusDimmer();
|
|
sendDelayedStatus();
|
|
return INTERVAL_CHECK_MODBUS;
|
|
break;
|
|
case CH_VC:
|
|
checkFM();
|
|
sendDelayedStatus();
|
|
return INTERVAL_CHECK_MODBUS;
|
|
break;
|
|
case CH_RGB: //All channels with slider generate too many updates
|
|
case CH_RGBW:
|
|
case CH_DIMMER:
|
|
case CH_PWM:
|
|
case CH_VCTEMP:
|
|
case CH_THERMO:
|
|
sendDelayedStatus();
|
|
}
|
|
return INTERVAL_POLLING;
|
|
}
|
|
|
|
void Item::sendDelayedStatus(){
|
|
HSVstore st;
|
|
int cmd=getCmd();
|
|
short params = 0;
|
|
int Par[3];
|
|
if (cmd & CMD_REPORT)
|
|
{
|
|
//retrive stored values
|
|
st.aslong = getVal();
|
|
switch (itemType) {
|
|
//case CH_GROUP:
|
|
case CH_RGBW:
|
|
case CH_RGB:
|
|
Par[0] = st.h;
|
|
Par[1] = st.s;
|
|
Par[2] = st.v;
|
|
params = 3;
|
|
SendStatus(0, params, Par); // Send restored triplet.
|
|
break;
|
|
|
|
case CH_VCTEMP:
|
|
case CH_PWM:
|
|
case CH_DIMMER: //Everywhere, in flat VAL
|
|
case CH_MODBUS:
|
|
case CH_VC:
|
|
case CH_THERMO:
|
|
|
|
|
|
Par[0] = st.aslong;
|
|
params = 1;
|
|
SendStatus(0, params, Par); // Send restored parameter
|
|
break;
|
|
default:
|
|
SendStatus(cmd); // Just send CMD
|
|
}//itemtype
|
|
cmd &= ~CMD_REPORT; // Clean report flag
|
|
setCmd(cmd);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
int Item::SendStatus(short cmd, short n, int *Par, boolean deffered) {
|
|
|
|
/// ToDo: relative patches, configuration
|
|
|
|
if (deffered) {
|
|
setCmd(cmd | CMD_REPORT);
|
|
Serial.println(F("Status deffered"));
|
|
// mqttClient.publish("/push", "1");
|
|
return 0;
|
|
// Todo: Parameters? Now expected that parameters already stored by setVal()
|
|
}
|
|
else { //publush to MQTT
|
|
char addrstr[32];
|
|
//char addrbuf[17];
|
|
char valstr[16] = "";
|
|
|
|
strcpy_P(addrstr, outprefix);
|
|
strncat(addrstr, itemArr->name, sizeof(addrstr));
|
|
|
|
|
|
switch (cmd) {
|
|
case CMD_ON:
|
|
strcpy(valstr, "ON");
|
|
break;
|
|
case CMD_OFF:
|
|
case CMD_HALT:
|
|
strcpy(valstr, "OFF");
|
|
break;
|
|
// TODO send Par
|
|
case 0:
|
|
case CMD_SET:
|
|
if (Par)
|
|
for (short i = 0; i < n; i++) {
|
|
char num[4];
|
|
snprintf(num, sizeof(num), "%d", Par[i]);
|
|
strncat(valstr, num, sizeof(valstr));
|
|
if (i != n - 1) {
|
|
strcpy(num, ",");
|
|
strncat(valstr, num, sizeof(valstr));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
Serial.println(F("Unknown cmd "));
|
|
return -1;
|
|
}
|
|
Serial.print(F("Pub: "));
|
|
Serial.print(addrstr);
|
|
Serial.print(F("->"));
|
|
Serial.println(valstr);
|
|
mqttClient.publish(addrstr, valstr,true);
|
|
return 0;
|
|
}
|
|
}
|