From 118fd25186aa7687537d1205d801c821f10e4abc Mon Sep 17 00:00:00 2001 From: Andrey Klimov Date: Fri, 13 Nov 2020 14:19:30 +0300 Subject: [PATCH] UNTESTED interim commit with refactoring --- compiled/lighthub21/2020-10-27 23.51.11.jpg | Bin 0 -> 55370 bytes lighthub/abstractout.h | 3 +- lighthub/item.cpp | 402 +++++++--------- lighthub/item.h | 129 +---- lighthub/itemCmd.cpp | 500 ++++++++++++++++++++ lighthub/itemCmd.h | 170 +++++++ lighthub/main.cpp | 1 + lighthub/modules/out_ac.cpp | 31 +- lighthub/modules/out_ac.h | 3 +- lighthub/modules/out_dmx.cpp | 226 +++++++++ lighthub/modules/out_dmx.h | 34 ++ lighthub/modules/out_modbus.cpp | 37 +- lighthub/modules/out_modbus.h | 4 +- lighthub/modules/out_motor.cpp | 46 +- lighthub/modules/out_motor.h | 3 +- lighthub/modules/out_spiled.cpp | 122 ++--- lighthub/modules/out_spiled.h | 5 +- lighthub/statusled.cpp | 89 ++++ lighthub/statusled.h | 50 ++ lighthub/streamlog.cpp | 2 +- lighthub/textconst.h | 4 +- lighthub/utils.cpp | 67 --- lighthub/utils.h | 29 -- platformio.ini | 8 +- 24 files changed, 1373 insertions(+), 592 deletions(-) create mode 100644 compiled/lighthub21/2020-10-27 23.51.11.jpg create mode 100644 lighthub/itemCmd.cpp create mode 100644 lighthub/itemCmd.h create mode 100644 lighthub/modules/out_dmx.cpp create mode 100644 lighthub/modules/out_dmx.h create mode 100644 lighthub/statusled.cpp create mode 100644 lighthub/statusled.h diff --git a/compiled/lighthub21/2020-10-27 23.51.11.jpg b/compiled/lighthub21/2020-10-27 23.51.11.jpg new file mode 100644 index 0000000000000000000000000000000000000000..146033d71b30e26c52ab42587ac78c2416b7c926 GIT binary patch literal 55370 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%0D(YoFx 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 literal 0 HcmV?d00001 diff --git a/lighthub/abstractout.h b/lighthub/abstractout.h index 39c1bb2..e88226a 100644 --- a/lighthub/abstractout.h +++ b/lighthub/abstractout.h @@ -1,13 +1,14 @@ #pragma once #include "Arduino.h" #include "abstractch.h" +#include "itemCmd.h" class Item; class chPersistent {}; class abstractOut : public abstractCh{ public: abstractOut(Item * _item):abstractCh(){item=_item;}; - virtual int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) =0; + virtual int Ctrl(itemCmd cmd, int suffixCode=0, char* subItem=NULL) =0; virtual int isActive(){return 0;}; virtual int getDefaultOnVal(){return 100;}; virtual int getChanType(){return 0;} diff --git a/lighthub/item.cpp b/lighthub/item.cpp index 059d1fe..64320fc 100644 --- a/lighthub/item.cpp +++ b/lighthub/item.cpp @@ -55,134 +55,6 @@ extern lan_status lanStatus; int retrieveCode(char **psubItem); - - itemCmd itemCmd::Percents(int i) - { - type=ST_PERCENTS; - param.aslong=i; - return *this; - } - - itemCmd itemCmd::Int(int32_t i) - { - type=ST_INT32; - param.asInt32=i; - return *this; - } - - itemCmd itemCmd::Int(uint32_t i) - { - type=ST_UINT32; - param.asUint32=i; - return *this; - } - - - itemCmd itemCmd::Cmd(uint8_t i) - { - type=ST_COMMAND; - param.cmd_code=i; - return *this; - } - - char * itemCmd::toString(char * Buffer, int bufLen) - { - if (!Buffer) return NULL; - switch (type) - { - case ST_VOID: - return NULL; - - case ST_PERCENTS: - case ST_PERCENTS255: - case ST_UINT32: - snprintf(Buffer, bufLen, "%u", param.asUint32); - break; - case ST_INT32: - snprintf(Buffer, bufLen, "%d", param.asInt32); - - break; - case ST_HS: - case ST_HSV: - case ST_HSV255: - snprintf(Buffer, bufLen, "%d,%d,%d", param.h, param.s, param.v); - - break; - case ST_FLOAT_CELSIUS: - case ST_FLOAT_FARENHEIT: - case ST_FLOAT: - snprintf(Buffer, bufLen, "%d", param.asfloat); - break; - case ST_RGB: - snprintf(Buffer, bufLen, "%d,%d,%d", param.r, param.g, param.b); - break; - - case ST_RGBW: - snprintf(Buffer, bufLen, "%d,%d,%d", param.r, param.g, param.b,param.w); - break; - - case ST_STRING: - strncpy(Buffer, param.asString,bufLen); - - break; - case ST_COMMAND: - strncpy_P(Buffer, commands_P[param.cmd_code], bufLen); - } - return Buffer; - } - -int txt2cmd(char *payload) { - int cmd = CMD_UNKNOWN; - if (!payload || !payload[0]) return cmd; - - // Check for command - if (*payload == '-' || (*payload >= '0' && *payload <= '9')) cmd = CMD_NUM; - else if (*payload == '%') cmd = CMD_UP; -/* - else if (strcmp_P(payload, ON_P) == 0) cmd = CMD_ON; - else if (strcmp_P(payload, OFF_P) == 0) cmd = CMD_OFF; - else if (strcmp_P(payload, REST_P) == 0) cmd = CMD_RESTORE; - else if (strcmp_P(payload, TOGGLE_P) == 0) cmd = CMD_TOGGLE; - else if (strcmp_P(payload, HALT_P) == 0) cmd = CMD_HALT; - else if (strcmp_P(payload, XON_P) == 0) cmd = CMD_XON; - else if (strcmp_P(payload, XOFF_P) == 0) cmd = CMD_XOFF; - else if (strcmp_P(payload, HEAT_P) == 0) cmd = CMD_HEAT; - else if (strcmp_P(payload, COOL_P) == 0) cmd = CMD_COOL; - else if (strcmp_P(payload, AUTO_P) == 0) cmd = CMD_AUTO; - else if (strcmp_P(payload, FAN_ONLY_P) == 0) cmd = CMD_FAN; - else if (strcmp_P(payload, DRY_P) == 0) cmd = CMD_DRY; - else if (strcmp_P(payload, TRUE_P) == 0) cmd = CMD_ON; - else if (strcmp_P(payload, FALSE_P) == 0) cmd = CMD_OFF; - else if (strcmp_P(payload, ENABLED_P) == 0) cmd = CMD_ON; - else if (strcmp_P(payload, DISABLED_P) == 0) cmd = CMD_OFF; - else if (strcmp_P(payload, INCREASE_P) == 0) cmd = CMD_UP; - else if (strcmp_P(payload, DECREASE_P) == 0) cmd = CMD_DN; - else if (strcmp_P(payload, HIGH_P) == 0) cmd = CMD_HIGH; - else if (strcmp_P(payload, MED_P) == 0) cmd = CMD_MED; - else if (strcmp_P(payload, LOW_P) == 0) cmd = CMD_LOW; -*/ - else if (*payload == '{') cmd = CMD_JSON; - else if (*payload == '#') cmd = CMD_RGB; - else - { - for(uint8_t i=1; i (SERIAL_8N1); - } - - /* - else if (strncmp_P(payload, HSV_P, strlen (HSV_P)) == 0) cmd = CMD_HSV; - else if (strncmp_P(payload, RGB_P, strlen (RGB_P)) == 0) cmd = CMD_RGB; - */ - - return cmd; -} - int subitem2cmd(char *payload) { int cmd = 0; @@ -220,6 +92,7 @@ int txt2subItem(char *payload) { else if (strcmp_P(payload, FAN_P) == 0) cmd = S_FAN; else if (strcmp_P(payload, HUE_P) == 0) cmd = S_HUE; else if (strcmp_P(payload, SAT_P) == 0) cmd = S_SAT; + else if (strcmp_P(payload, TEMP_P) == 0) cmd = S_TEMP; /* UnUsed now else if (strcmp_P(payload, SETPOINT_P) == 0) cmd = S_SETPOINT; else if (strcmp_P(payload, TEMP_P) == 0) cmd = S_TEMP; @@ -426,6 +299,16 @@ long int Item::getVal() //Return Val if val is int or first elem of Value array } else return 0;//-2; } +uint8_t Item::getSubtype() +{ + if (!itemVal) return 0;//-1; + if (itemVal->type == aJson_Int) return itemVal->subtype; + else if (itemVal->type == aJson_Array) { + aJsonObject *t = aJson.getArrayItem(itemVal, 0); + if (t) return t->subtype; + else return 0;//-3; + } else return 0;//-2; +} /* void Item::setVal(short n, int par) // Only store if VAL is array defined in config to avoid waste of RAM { @@ -445,6 +328,12 @@ void Item::setVal(long int par) // Only store if VAL is int (autogenerated or c itemVal->valueint = par; } +void Item::setSubtype(uint8_t par) // Only store if VAL is int (autogenerated or config-defined) +{ + if (!itemVal || itemVal->type != aJson_Int) return; + //debugSerial<0); + //threating Toggle, Restore, XOFF special conditional commands/ convert to ON, OFF - switch (cmd.toCmd()) { + switch (cmd.getCmd()) { int t; case CMD_TOGGLE: if (chActive) cmd.Cmd(CMD_OFF); - else cmd.Cmd(CMD_ON); + else cmd.Cmd(CMD_ON); break; case CMD_RESTORE: @@ -718,117 +609,128 @@ int Ctrl(itemCmd cmd, int suffixCode, char* subItem) case CMD_UP: { if (itemType == CH_GROUP) break; ////bug here - if (!n || !Par[0]) Par[0] = DEFAULT_INC_STEP; - if (cmd == CMD_DN) Par[0]=-Par[0]; - st.aslong = getVal(); - int cType=getChanType(); + short step=cmd.getCmdParam(); + if (!step) step=DEFAULT_INC_STEP; + if (cmd.getCmd() == CMD_DN) step=-step; - debugSerial<<"from: h="<Ctrl(cmd.Cmd(CMD_ON), suffixCode, subItem); + setCmd(CMD_XON); + } + else + { //cmd = CMD_ON; + debugSerial<0) //if channel was active before CMD_HALT + { + res = driver->Ctrl(cmd.Cmd(CMD_OFF), suffixCode, subItem); + setCmd(CMD_HALT); + return res; + } + else + { + debugSerial<Ctrl(cmd.Cmd(CMD_OFF), suffixCode, subItem); + setCmd(CMD_OFF); + } + else + { + debugSerial<Ctrl(st, suffixCode, subItem); + break; + + default: //another command + res = driver->Ctrl(cmd, suffixCode, subItem); + if (cmd.isCommand()) setCmd(cmd.getCmd()); } - - - - -========= - int chActive = item->isActive(); + return res; + } +// Legacy monolite core code +//================== +switch (itemType) { + case CH_GROUP://Group + { + if (itemArg->type == aJson_Array) { + aJsonObject *i = itemArg->child; + configLocked++; + while (i) { + if (i->type == aJson_String) + { + Item it(i->valuestring); + it.Ctrl(cmd, suffixCode,subItem); + } + i = i->next; + } //while + configLocked--; + } //if + } //case + } //switch +//========= +/* bool toExecute = (chActive>0); - long st; + if (cmd>0 && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it @@ -919,6 +821,27 @@ bool send = isNotRetainingStatus() ; } debugSerial<0) //if channel was'nt active before CMD_XON { debugSerial<Ctrl(CMD_ON, n, Par, suffixCode, subItem); + //res = driver->Ctrl(CMD_ON, n, Par, suffixCode, subItem); + res = driver->Ctrl(_itemCmd.Cmd(CMD_ON), suffixCode, subItem); setCmd(CMD_XON); } else @@ -1127,7 +1052,7 @@ bool send = isNotRetainingStatus() ; case CMD_HALT: if (chActive>0) //if channel was active before CMD_HALT { - res = driver->Ctrl(CMD_OFF, n, Par, suffixCode, subItem); + res = driver->Ctrl(_itemCmd.Cmd(CMD_OFF), suffixCode, subItem); setCmd(CMD_HALT); return res; } @@ -1140,7 +1065,7 @@ bool send = isNotRetainingStatus() ; case CMD_OFF: if (getCmd() != CMD_HALT) //Halted, ignore OFF { - res = driver->Ctrl(cmd, n, Par, suffixCode, subItem); + res = driver->Ctrl(_itemCmd, suffixCode, subItem); setCmd(CMD_OFF); } else @@ -1155,7 +1080,7 @@ bool send = isNotRetainingStatus() ; break; */ default: - res = driver->Ctrl(cmd, n, Par, suffixCode, subItem); + res = driver->Ctrl(_itemCmd, suffixCode, subItem); if (cmd) setCmd(cmd); } return res; @@ -1180,7 +1105,7 @@ bool send = isNotRetainingStatus() ; case 1: st.v = Par[0]; //Volume only - if (st.hsv_flag) + if (getSubtype()==ST_HSV || getSubtype()==ST_RGB) { Par[0] = st.h; Par[1] = st.s; @@ -1191,14 +1116,16 @@ bool send = isNotRetainingStatus() ; st.h = Par[0]; st.s = Par[1]; Par[2] = st.v; - st.hsv_flag = 1; + //st.hsv_flag = 1; + setSubtype(ST_HSV); n = 3; break; case 3: //complete triplet st.h = Par[0]; st.s = Par[1]; st.v = Par[2]; - st.hsv_flag = 1; + //st.hsv_flag = 1; + setSubtype(ST_HSV); } setVal(st.aslong); if (!suffixCode) @@ -2191,7 +2118,8 @@ int Item::SendStatus(int sendFlags) { snprintf(valstr, sizeof(valstr), "%d,%d,%d", st.h,st.s,st.v); break; case CH_GROUP: - if (st.hsv_flag) + //if (st.hsv_flag) + if (getSubtype()==ST_HSV) snprintf(valstr, sizeof(valstr), "%d,%d,%d", st.h,st.s,st.v); else snprintf(valstr, sizeof(valstr), "%d", st.v); @@ -2200,7 +2128,7 @@ int Item::SendStatus(int sendFlags) { sendFlags &= ~SEND_PARAMETERS; //No need to send value for relay break; default: - snprintf(valstr, sizeof(valstr), "%d", st.aslong); + snprintf(valstr, sizeof(valstr), "%ld", st.aslong); }//itemtype } if (sendFlags & SEND_COMMAND) diff --git a/lighthub/item.h b/lighthub/item.h index 0a37d19..2f6be9b 100644 --- a/lighthub/item.h +++ b/lighthub/item.h @@ -20,10 +20,7 @@ e-mail anklimov@gmail.com #pragma once #include "options.h" #include "abstractout.h" - -#define POLLING_SLOW 1 -#define POLLING_FAST 2 -#define POLLING_INT 3 +#include "itemCmd.h" #define S_NOTFOUND 0 #define S_SETnCMD 0 @@ -35,6 +32,7 @@ e-mail anklimov@gmail.com #define S_MODE 6 #define S_HUE 7 #define S_SAT 8 +#define S_TEMP 9 #define S_ADDITIONAL 64 #define CH_DIMMER 0 //DMX 1 ch @@ -58,64 +56,14 @@ e-mail anklimov@gmail.com #define CH_WHITE 127// -#define CMD_NUM 0 -#define CMD_UNKNOWN -1 -#define CMD_JSON -2 -//#define CMD_RGB -3 -//#define CMD_HSV -4 - -typedef char cmdstr[9]; - -const cmdstr commands_P[] PROGMEM = -{ -"","ON","OFF","REST","TOGGLE","HALT","XON","XOFF","INCREASE","DECREASE", -"HEAT","COOL","AUTO","FAN_ONLY","DRY","STOP","HIGH","MEDIUM","LOW", -"TRUE","FALSE","ENABLED","DISABLED","RGB","HSV" -}; -#define commandsNum sizeof(commands_P)/sizeof(cmdstr) - -#define CMD_ON 1 -#define CMD_OFF 2 -#define CMD_RESTORE 3 //on only if was turned off by CMD_HALT -#define CMD_TOGGLE 4 -#define CMD_HALT 5 //just Off -#define CMD_XON 6 //just on -#define CMD_XOFF 7 //off only if was previously turned on by CMD_XON -#define CMD_UP 8 //increase -#define CMD_DN 9 //decrease -#define CMD_HEAT 0xa -#define CMD_COOL 0xb -#define CMD_AUTO 0xc -#define CMD_FAN 0xd -#define CMD_DRY 0xe -#define CMD_STOP 0xf -#define CMD_HIGH 0x10 //AC fan leve -#define CMD_MED 0x11 -#define CMD_LOW 0x12 -#define CMD_ENABLED 0x13 -#define CMD_DISABLED 0x14 -#define CMD_TRUE 0x15 -#define CMD_FALSE 0x16 -#define CMD_RGB 0x17 -#define CMD_HSV 0x18 -//#define CMD_CURTEMP 0xf -#define CMD_MASK 0xff -#define FLAG_MASK 0xff00 - - -#define SEND_COMMAND 0x100 -#define SEND_PARAMETERS 0x200 -#define SEND_RETRY 0x400 -#define SEND_DEFFERED 0x800 -#define ACTION_NEEDED 0x1000 -#define ACTION_IN_PROCESS 0x2000 +#define POLLING_SLOW 1 +#define POLLING_FAST 2 +#define POLLING_INT 3 -//#define CMD_REPORT 32 - #define I_TYPE 0 //Type of item #define I_ARG 1 //Chanel-type depended argument or array of arguments (pin, address etc) #define I_VAL 2 //Latest preset (int or array of presets) @@ -140,71 +88,6 @@ extern short thermoSetCurTemp(char *name, float t); int txt2cmd (char * payload); -enum itemStoreType { -ST_VOID = 0, -ST_PERCENTS = 1, -ST_HS = 2, -ST_HSV = 3, -ST_FLOAT_CELSIUS= 4, -ST_FLOAT_FARENHEIT= 5, -ST_RGB = 6, -ST_RGBW = 7, -ST_PERCENTS255 = 8, -ST_HSV255 = 9, -ST_INT32 = 10, -ST_UINT32 = 11, -ST_STRING = 12, -ST_FLOAT = 13, -ST_COMMAND = 15 - -}; - -#pragma pack(push, 1) -typedef union -{ - long int aslong; - int32_t asInt32; - uint32_t asUint32; - char* asString; - float asfloat; - struct - { - uint8_t cmd_code; - uint8_t cmd_flag; - uint8_t cmd_effect; - uint8_t cmd_effect_param; - }; - struct - { uint8_t v; - uint8_t s; - uint16_t h:15; - uint16_t hsv_flag:1; - }; - struct - { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t w;//:7; -// uint8_t rgb_flag:1; - }; -} itemStore; - -class itemCmd -{ -public: - itemStoreType type; - itemStore param; - itemCmd Percents(int i); - itemCmd Int(int32_t i); - itemCmd Int(uint32_t i); - itemCmd Cmd(uint8_t i); - char * toString(char * Buffer, int bufLen); - short toCmd(); - } ; - -#pragma pack(pop) - class Item { public: @@ -226,6 +109,7 @@ class Item int getArg(short n=0); //int getVal(short n); //From VAL array. Negative if no array long int getVal(); //From int val OR array + uint8_t getSubtype(); uint8_t getCmd(); long int getExt(); //From int val OR array void setExt(long int par); @@ -236,6 +120,7 @@ class Item void setFlag (short flag); void clearFlag (short flag); void setVal(long int par); + void setSubtype(uint8_t par); int Poll(int cause); int SendStatus(int sendFlags); int isActive(); diff --git a/lighthub/itemCmd.cpp b/lighthub/itemCmd.cpp new file mode 100644 index 0000000..aeed57a --- /dev/null +++ b/lighthub/itemCmd.cpp @@ -0,0 +1,500 @@ +#include +#include "itemCmd.h" +#include "main.h" +#include "Streaming.h" +#include "item.h" + +#ifdef ADAFRUIT_LED +#include +#else +#include "FastLED.h" +#endif + +int txt2cmd(char *payload) { + int cmd = CMD_UNKNOWN; + if (!payload || !payload[0]) return cmd; + + // Check for command + if (*payload == '-' || (*payload >= '0' && *payload <= '9')) cmd = CMD_NUM; + else if (*payload == '%') cmd = CMD_UP; + else if (*payload == '{') cmd = CMD_JSON; + else if (*payload == '#') cmd = CMD_RGB; + else + { + for(uint8_t i=1; i100) par=100; + case ST_HSV255: + if (par>255) par=255; + if (par<0) par=0; + param.h=par; + } + return *this; +} + +itemCmd itemCmd::setS(uint8_t s) +{ + int par=s; + switch (type) + { + case ST_VOID: + type=ST_HSV; + case ST_HSV: + if (par>100) par=100; + case ST_HSV255: + if (par>255) par=255; + if (par<0) par=0; + param.s=par; + } + return *this; +} + +itemCmd itemCmd::incrementPercents(int16_t dif) +{ int par=param.v; + switch (type) + { + case ST_PERCENTS: + case ST_HSV: + par+=dif; + if (par>100) par=100; + if (par<0) par=0; + break; + case ST_PERCENTS255: + case ST_HSV255: + par+=dif; + if (par>255) par=255; + if (par<0) par=0; + break; + } + param.v=par; + return *this; +} + +itemCmd itemCmd::incrementH(int16_t dif) +{ int par=param.h; + switch (type) + { + case ST_HSV: + case ST_HSV255: + par+=dif; + if (par>365) par=0; + if (par<0) par=365; + break; +} +param.h=par; +return *this; +} + +itemCmd itemCmd::incrementS(int16_t dif) +{int par=param.s; + switch (type) + { + case ST_PERCENTS: + case ST_HSV: + par+=dif; + if (par>100) par=100; + if (par<0) par=0; + break; + case ST_PERCENTS255: + case ST_HSV255: + par+=dif; + if (par>255) par=255; + if (par<0) par=0; + break; + } + param.s=par; + return *this; + +} + + +itemCmd itemCmd::assignFrom(itemCmd from) +{ + bool RGBW_flag = false; + bool HSV255_flag = false; + + switch (type){ //Destination + case ST_HSV: + case ST_PERCENTS: + switch (from.type) + { + case ST_RGBW: + param.w=from.param.w; + case ST_RGB: + param.r=from.param.r; + param.g=from.param.g; + param.b=from.param.b; + type=from.type; //Changing if type + break; + case ST_HSV: + param.h=from.param.h; + param.s=from.param.s; + param.v=from.param.v; + break; + case ST_PERCENTS: + param.v=from.param.v; + break; + case ST_HSV255: + param.h=from.param.h; + param.s=map(from.param.s,0,255,0,100); + param.v=map(from.param.v,0,255,0,100); + break; + case ST_PERCENTS255: + param.v=map(from.param.v,0,255,0,100); + break; + default: + debugSerial<")<")<255) rgbValue = 255; + } + else + { + rgbSaturation = map(rgbSaturation, 128, 255, 100, 255); + param.w=0; + } + } + #ifdef ADAFRUIT_LED + Adafruit_NeoPixel strip(0, 0, 0); + uint32_t rgb = strip.ColorHSV(map(from.param.h, 0, 365, 0, 65535), rgbSaturation, rgbValue); + param.r=(rgb >> 16)& 0xFF; + param.g=(rgb >> 8) & 0xFF; + param.b=rgb & 0xFF; + #else + CRGB rgb = CHSV(map(from.param.h, 0, 365, 0, 255), rgbSaturation, rgbValue); + param.r=rgb.r; + param.g=rgb.g; + param.b=rgb.b; + #endif + } + default: + debugSerial<")<isValid()) + { + param.asInt32=item->getVal(); + type=(itemStoreType) item->getSubtype(); + return (type!=ST_VOID); + } +return false; +} + +bool itemCmd::saveItem(Item * item) +{ + if (item && item->isValid()) + { + item->setVal(param.asInt32); + item->setSubtype(type); + return true; + } +return false; +} + + + +char * itemCmd::toString(char * Buffer, int bufLen) + { + if (!Buffer) return NULL; + switch (type) + { + case ST_VOID: + return NULL; + + case ST_PERCENTS: + case ST_PERCENTS255: + case ST_UINT32: + snprintf(Buffer, bufLen, "%lu", param.asUint32); + break; + case ST_INT32: + snprintf(Buffer, bufLen, "%ld", param.asInt32); + + break; + case ST_HSV: + case ST_HSV255: + snprintf(Buffer, bufLen, "%d,%d,%d", param.h, param.s, param.v); + + break; + case ST_FLOAT_CELSIUS: + case ST_FLOAT_FARENHEIT: + case ST_FLOAT: + snprintf(Buffer, bufLen, "%.1f", param.asfloat); + break; + case ST_RGB: + snprintf(Buffer, bufLen, "%d,%d,%d", param.r, param.g, param.b); + break; + + case ST_RGBW: + snprintf(Buffer, bufLen, "%d,%d,%d,%d", param.r, param.g, param.b,param.w); + break; + + case ST_STRING: + strncpy(Buffer, param.asString,bufLen); + + break; + case ST_COMMAND: + strncpy_P(Buffer, commands_P[param.cmd_code], bufLen); + } + return Buffer; + } diff --git a/lighthub/itemCmd.h b/lighthub/itemCmd.h new file mode 100644 index 0000000..32a0cf6 --- /dev/null +++ b/lighthub/itemCmd.h @@ -0,0 +1,170 @@ +/* Copyright © 2017-2020 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 + +*/ +#pragma once +#include "Arduino.h" + +typedef char cmdstr[9]; + +const cmdstr commands_P[] PROGMEM = +{ +"","ON","OFF","REST","TOGGLE","HALT","XON","XOFF","INCREASE","DECREASE", +"HEAT","COOL","AUTO","FAN_ONLY","DRY","STOP","HIGH","MEDIUM","LOW", +"TRUE","FALSE","ENABLED","DISABLED","RGB","HSV" +}; +#define commandsNum sizeof(commands_P)/sizeof(cmdstr) + +#define CMD_ON 1 +#define CMD_OFF 2 +#define CMD_RESTORE 3 //on only if was turned off by CMD_HALT +#define CMD_TOGGLE 4 +#define CMD_HALT 5 //just Off +#define CMD_XON 6 //just on +#define CMD_XOFF 7 //off only if was previously turned on by CMD_XON +#define CMD_UP 8 //increase +#define CMD_DN 9 //decrease +#define CMD_HEAT 0xa +#define CMD_COOL 0xb +#define CMD_AUTO 0xc +#define CMD_FAN 0xd +#define CMD_DRY 0xe +#define CMD_STOP 0xf +#define CMD_HIGH 0x10 //AC fan leve +#define CMD_MED 0x11 +#define CMD_LOW 0x12 +#define CMD_ENABLED 0x13 +#define CMD_DISABLED 0x14 +#define CMD_TRUE 0x15 +#define CMD_FALSE 0x16 +#define CMD_RGB 0x17 +#define CMD_HSV 0x18 +//#define CMD_CURTEMP 0xf +#define CMD_MASK 0xff +#define FLAG_MASK 0xff00 + +#define CMD_NUM 0 +#define CMD_UNKNOWN -1 +#define CMD_JSON -2 +//#define CMD_RGB -3 +//#define CMD_HSV -4 + +#define SEND_COMMAND 0x100 +#define SEND_PARAMETERS 0x200 +#define SEND_RETRY 0x400 +#define SEND_DEFFERED 0x800 +#define ACTION_NEEDED 0x1000 +#define ACTION_IN_PROCESS 0x2000 + + +int txt2cmd (char * payload); + +enum itemStoreType { +ST_VOID = 0, +ST_PERCENTS = 1, +ST_TENS = 2, +ST_HSV = 3, +ST_FLOAT_CELSIUS= 4, +ST_FLOAT_FARENHEIT= 5, +ST_RGB = 6, +ST_RGBW = 7, +ST_PERCENTS255 = 8, +ST_HSV255 = 9, +ST_INT32 = 10, +ST_UINT32 = 11, +ST_STRING = 12, +ST_FLOAT = 13, +ST_COMMAND = 15 + +}; + +#pragma pack(push, 1) +typedef union +{ + long int aslong; + int32_t asInt32; + uint32_t asUint32; + char* asString; + float asfloat; + struct + { + uint8_t cmd_code; + uint8_t cmd_flag; + uint8_t cmd_effect; + uint8_t cmd_param; + }; + struct + { uint8_t v; + uint8_t s; + uint16_t h:9; + uint16_t colorTemp:7; + }; + struct + { int8_t signed_v; + int8_t signed_s; + int16_t signed_h:9; + int16_t signed_colorTemp:7; + }; + struct + { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t w; + }; +} itemStore; +class Item; +class itemCmd +{ +public: + itemStoreType type; + itemStore param; + + itemCmd(itemStoreType _type=ST_VOID); + itemCmd assignFrom(itemCmd from); + + bool loadItem(Item * item); + bool saveItem(Item * item); + + itemCmd Int(int32_t i); + itemCmd Int(uint32_t i); + itemCmd Cmd(uint8_t i); + itemCmd HSV(uint16_t h, uint8_t s, uint8_t v); + itemCmd setH(uint16_t); + itemCmd setS(uint8_t); + itemCmd Percents(int i); + + itemCmd incrementPercents(int16_t); + itemCmd incrementH(int16_t); + itemCmd incrementS(int16_t); + + long int getInt(); + short getPercents(); + short getPercents255(); + short getCmd(); + short getCmdParam(); + char * toString(char * Buffer, int bufLen); + + bool isCommand(); + bool isValue(); + bool isHSV(); + + itemCmd setDefault(); + }; + +#pragma pack(pop) diff --git a/lighthub/main.cpp b/lighthub/main.cpp index 9f55adf..0babfdb 100644 --- a/lighthub/main.cpp +++ b/lighthub/main.cpp @@ -66,6 +66,7 @@ PWM Out */ #include "main.h" +#include "statusled.h" #ifdef WIFI_ENABLE WiFiClient ethClient; diff --git a/lighthub/modules/out_ac.cpp b/lighthub/modules/out_ac.cpp index 4e4f161..7dab40b 100644 --- a/lighthub/modules/out_ac.cpp +++ b/lighthub/modules/out_ac.cpp @@ -129,7 +129,7 @@ void out_AC::InsertData(byte data[], size_t size){ ///////////////////////////////// publishTopic(item->itemArr->name,(long)set_tmp,"/set"); - publishTopic(item->itemArr->name, (long)cur_tmp, "/temp"); + if (cur_tmp!=255) publishTopic(item->itemArr->name, (long)cur_tmp, "/temp"); //////////////////////////////////// s_mode[0]='\0'; @@ -148,6 +148,9 @@ void out_AC::InsertData(byte data[], size_t size){ else if (mode == 0x04){ strcpy_P(s_mode,DRY_P); } + else if (mode == 109){ + strcpy_P(s_mode,ERROR_P); + } publishTopic(item->itemArr->name, (long) mode, "/mode"); @@ -259,7 +262,8 @@ delay(100); return INTERVAL_POLLING; }; -int out_AC::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* subItem) +//int out_AC::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* subItem) +int out_AC::Ctrl(itemCmd cmd, int suffixCode, char* subItem) {char s_mode[10]; // Some additional Subitems if (strcmp_P(subItem, LOCK_P) == 0) suffixCode = S_LOCK; @@ -267,12 +271,14 @@ int out_AC::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* su else if (strcmp_P(subItem, QUIET_P) == 0) suffixCode = S_QUIET; else if (strcmp_P(subItem, RAW_P) == 0) suffixCode = S_RAW; + if (cmd.isCommand() && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it + //data[B_POWER] = power; // debugSerial<= 10 && set_tmp <= 30) { data[B_SET_TMP] = set_tmp -16; @@ -282,7 +288,7 @@ int out_AC::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* su case S_CMD: s_mode[0]='\0'; - switch (cmd) + switch (cmd.getCmd()) { case CMD_ON: case CMD_XON: @@ -340,7 +346,7 @@ int out_AC::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* su case S_FAN: s_mode[0]='\0'; - switch (cmd) + switch (cmd.getCmd()) { case CMD_AUTO: data[B_FAN_SPD] = 3; @@ -359,18 +365,20 @@ int out_AC::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* su strcpy_P(s_mode,LOW_P); break; default: - if (n) data[B_FAN_SPD] = Parameters[0]; + //if (n) data[B_FAN_SPD] = Parameters[0]; + data[B_FAN_SPD] = cmd.getInt(); //TODO - mapping digits to speed } publishTopic(item->itemArr->name,s_mode,"/fan"); break; case S_MODE: - data[B_MODE] = Parameters[0]; + //data[B_MODE] = Parameters[0]; + data[B_MODE] = cmd.getInt(); break; case S_LOCK: - switch (cmd) + switch (cmd.getCmd()) { case CMD_ON: data[B_LOCK_REM] = 80; @@ -382,7 +390,7 @@ int out_AC::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* su break; case S_SWING: - switch (cmd) + switch (cmd.getCmd()) { case CMD_ON: data[B_LOCK_REM] = 3; @@ -391,12 +399,13 @@ int out_AC::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* su data[B_LOCK_REM] = 0; break; default: - if (n) data[B_SWING] = Parameters[0]; + //if (n) data[B_SWING] = Parameters[0]; + data[B_SWING] = cmd.getInt(); } break; case S_QUIET: - switch (cmd) + switch (cmd.getCmd()) { case CMD_ON: data[B_POWER] |= 8; diff --git a/lighthub/modules/out_ac.h b/lighthub/modules/out_ac.h index 33bd691..88aa161 100644 --- a/lighthub/modules/out_ac.h +++ b/lighthub/modules/out_ac.h @@ -29,7 +29,8 @@ public: int Stop() override; int Status() override; int isActive() override; - int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override; + //int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override; + int Ctrl(itemCmd cmd, int suffixCode=0, char* subItem=NULL) override; protected: void InsertData(byte data[], size_t size); diff --git a/lighthub/modules/out_dmx.cpp b/lighthub/modules/out_dmx.cpp new file mode 100644 index 0000000..d246153 --- /dev/null +++ b/lighthub/modules/out_dmx.cpp @@ -0,0 +1,226 @@ +//#ifndef DMX_DISABLE +#ifdef XXXX +#include "modules/out_dmx.h" +#include "Arduino.h" +#include "options.h" +#include "Streaming.h" + +#include "item.h" +#include "main.h" + +static int driverStatus = CST_UNKNOWN; + + + + +int out_dmx::Setup() +{ + +debugSerial<getVal(); //Restore old params +debugSerial<< F(" val:")<>4) == (ledsType>>6)) + return CH_RGB; + else + return CH_RGBW; +} + +int out_SPILed::PixelCtrl(itemCmd cmd, int from, int to, bool show) +{ +itemCmd st(ST_RGB); + +#ifdef ADAFRUIT_LED +uint32_t pixel; +#else +CRGB pixel; +#endif + + if (to>numLeds-1) to=numLeds-1; + if (from<0) from=0; + + for (int i=from;i<=to;i++) + { + switch (cmd.getCmd()) { + case CMD_ON: + + #ifdef ADAFRUIT_LED + if (!leds->getPixelColor(i)) leds->setPixelColor(i, leds->Color(255, 255, 255)); + #else + if (!leds[i].r && !leds[i].g &&!leds[i].b) leds[i] = CRGB::White; + #endif + break; + + case CMD_OFF: + #ifdef ADAFRUIT_LED + leds->setPixelColor(i, leds->Color(0, 0, 0)); + #else + leds[i] = CRGB::Black; + #endif + + break; + default: + st.assignFrom(cmd); + + #ifdef ADAFRUIT_LED + leds->setPixelColor(i, leds->Color(st.param.r,st.param.g,st.param.b)); + #else + leds[i] = CRGB(st.param.r,st.param.g,st.param.b); + #endif + } + } //for + + + + if (show) + { + #ifdef ADAFRUIT_LED + leds->show(); + #else + FastLED.show(); + #endif + + debugSerial<isActive(); +bool toExecute = (chActive>0); +itemCmd st(ST_HSV); +if (cmd.isCommand() && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it + +int from=0, to=numLeds-1; //All LEDs on the strip by default +// retrive LEDs range from suffix +if (subItem) +{ //Just single LED to control todo - range +// debugSerial<setVal(st.getInt()); + st.saveItem(item); + item->SendStatus(SEND_COMMAND | SEND_PARAMETERS ); + } + + PixelCtrl(st,from,to); + } + + return 1; + + case CMD_OFF: + if (subItem) // LED range, not whole strip + PixelCtrl(st.Cmd(CMD_OFF)); + else + { + st.Percents(0); + PixelCtrl(st,from,to); + item->SendStatus(SEND_COMMAND); + // if (send) item->publishTopic(item->itemArr->name,"OFF","/cmd"); + } + return 1; + +} //switch cmd + +break; +} //switch suffix +debugSerial< +#include + +#ifdef ADAFRUIT_LED +#include +#else +#include "FastLED.h" +#endif + +class out_dmx : public abstractOut { +public: + + out_dmx(Item * _item):abstractOut(_item){getConfig();}; + int Setup() override; + int Poll(short cause) override; + int Stop() override; + int Status() override; + int isActive() override; + int getChanType() override; + int Ctrl(itemCmd cmd, int suffixCode=0, char* subItem=NULL) override; + int PixelCtrl(itemCmd cmd, int from =0 , int to = 1024, bool show = 1); + int numLeds; + int8_t pin; + int ledsType; +protected: + void getConfig(); +}; +#endif diff --git a/lighthub/modules/out_modbus.cpp b/lighthub/modules/out_modbus.cpp index ec7861a..767b932 100644 --- a/lighthub/modules/out_modbus.cpp +++ b/lighthub/modules/out_modbus.cpp @@ -338,14 +338,12 @@ int out_Modbus::getChanType() -int out_Modbus::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* subItem) +int out_Modbus::Ctrl(itemCmd cmd, int suffixCode, char* subItem) { int chActive = item->isActive(); bool toExecute = (chActive>0); -long st; -if (cmd>0 && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it - -//item->setFlag(ACTION_NEEDED); +itemCmd st(ST_UINT32); +if (cmd.isCommand() && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it switch(suffixCode) { @@ -354,12 +352,12 @@ case S_NOTFOUND: toExecute = true; debugSerial<setVal(st=Parameters[0]); //Store + if (!cmd.isValue()) return 0; + //////item->setVal(st=Parameters[0]); //Store if (!suffixCode) { - if (chActive>0 && !st) item->setCmd(CMD_OFF); - if (chActive==0 && st) item->setCmd(CMD_ON); + if (chActive>0 && !st.getInt()) item->setCmd(CMD_OFF); + if (chActive==0 && st.getInt()) item->setCmd(CMD_ON); item->SendStatus(SEND_COMMAND | SEND_PARAMETERS | SEND_DEFFERED); // if (item->getExt()) item->setExt(millis()+maxOnTime); //Extend motor time } @@ -369,28 +367,29 @@ case S_SET: //break; case S_CMD: - item->setCmd(cmd); - switch (cmd) + //item->setCmd(cmd.getCmd()); + st.loadItem(item); + switch (cmd.getCmd()) { case CMD_ON: //retrive stored values - st = item->getVal(); + st.loadItem(item); - if (st && (stsetVal(st); + if (st.getPercents() && (st.getPercents()SendStatus(SEND_COMMAND | SEND_PARAMETERS); - debugSerial<setVal(st); + st.setDefault(); + st.saveItem(item); item->SendStatus(SEND_COMMAND | SEND_PARAMETERS ); } // if (item->getExt()) item->setExt(millis()+maxOnTime); //Extend motor time diff --git a/lighthub/modules/out_modbus.h b/lighthub/modules/out_modbus.h index af150c2..82110b6 100644 --- a/lighthub/modules/out_modbus.h +++ b/lighthub/modules/out_modbus.h @@ -31,8 +31,8 @@ public: int Status() override; int isActive() override; int getChanType() override; - int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override; - + int Ctrl(itemCmd cmd, int suffixCode=0, char* subItem=NULL) override; + //int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override; protected: mbPersistent * store; diff --git a/lighthub/modules/out_motor.cpp b/lighthub/modules/out_motor.cpp index 6394994..1082964 100644 --- a/lighthub/modules/out_motor.cpp +++ b/lighthub/modules/out_motor.cpp @@ -211,12 +211,12 @@ int out_Motor::getChanType() -int out_Motor::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* subItem) +int out_Motor::Ctrl(itemCmd cmd, int suffixCode, char* subItem) { int chActive = item->isActive(); bool toExecute = (chActive>0); -long st; -if (cmd>0 && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it +itemCmd st(ST_PERCENTS); +if (cmd.isCommand() && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it item->setFlag(ACTION_NEEDED); @@ -227,12 +227,14 @@ case S_NOTFOUND: toExecute = true; debugSerial<setVal(st=Parameters[0]); //Store + if (!cmd.isValue()) return 0; + st.assignFrom(cmd); + //Store + st.saveItem(item); if (!suffixCode) { - if (chActive>0 && !st) item->setCmd(CMD_OFF); - if (chActive==0 && st) item->setCmd(CMD_ON); + if (chActive>0 && !st.getPercents()) item->setCmd(CMD_OFF); + if (chActive==0 && st.getPercents()) item->setCmd(CMD_ON); item->SendStatus(SEND_COMMAND | SEND_PARAMETERS | SEND_DEFFERED); if (item->getExt()) item->setExt(millis()+maxOnTime); //Extend motor time } @@ -242,30 +244,32 @@ case S_SET: //break; case S_CMD: - item->setCmd(cmd); - switch (cmd) + item->setCmd(cmd.getCmd()); + switch (cmd.getCmd()) { case CMD_ON: //retrive stored values - st = item->getVal(); - - - if (st && (stsetVal(st); - - if (st) //Stored smthng + if (st.loadItem(item)) { - item->SendStatus(SEND_COMMAND | SEND_PARAMETERS); - debugSerial<SendStatus(SEND_COMMAND | SEND_PARAMETERS); + } + debugSerial<setVal(st); + st.setDefault(); + st.saveItem(item); + //st=100; + //item->setVal(st); item->SendStatus(SEND_COMMAND | SEND_PARAMETERS ); } + if (item->getExt()) item->setExt(millis()+maxOnTime); //Extend motor time return 1; diff --git a/lighthub/modules/out_motor.h b/lighthub/modules/out_motor.h index 156d623..3197368 100644 --- a/lighthub/modules/out_motor.h +++ b/lighthub/modules/out_motor.h @@ -25,7 +25,8 @@ public: int Status() override; int isActive() override; int getChanType() override; - int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override; + //int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override; + int Ctrl(itemCmd cmd, int suffixCode=0, char* subItem=NULL) override; int8_t pinUp; int8_t pinDown; diff --git a/lighthub/modules/out_spiled.cpp b/lighthub/modules/out_spiled.cpp index 3dba380..a7d73e2 100644 --- a/lighthub/modules/out_spiled.cpp +++ b/lighthub/modules/out_spiled.cpp @@ -108,35 +108,22 @@ int out_SPILed::getChanType() return CH_RGBW; } -int out_SPILed::PixelCtrl(itemStore *st, short cmd, int from, int to, bool show, bool rgb) +int out_SPILed::PixelCtrl(itemCmd cmd, int from, int to, bool show) { - //debugSerial<s, 0, 100, 0, 255); - int Value = map(st->v, 0, 100, 0, 255); -#ifdef ADAFRUIT_LED - uint16_t Hue = map(st->h, 0, 365, 0, 65535); - pixel = leds->ColorHSV(Hue, Saturation, Value); -#else - int Hue = map(st->h, 0, 365, 0, 255); - pixel = CHSV(Hue, Saturation, Value); -#endif - - debugSerial<h<s<v<numLeds-1) to=numLeds-1; if (from<0) from=0; for (int i=from;i<=to;i++) { - switch (cmd) { + switch (cmd.getCmd()) { case CMD_ON: #ifdef ADAFRUIT_LED @@ -144,9 +131,8 @@ CRGB pixel; #else if (!leds[i].r && !leds[i].g &&!leds[i].b) leds[i] = CRGB::White; #endif - - break; + case CMD_OFF: #ifdef ADAFRUIT_LED leds->setPixelColor(i, leds->Color(0, 0, 0)); @@ -156,26 +142,18 @@ CRGB pixel; break; default: - if (rgb) - { - #ifdef ADAFRUIT_LED - leds->setPixelColor(i, leds->Color(st->r,st->g,st->b)); - #else - leds[i] = CRGB(st->r,st->g,st->b); - #endif - } - else - { - #ifdef ADAFRUIT_LED - leds->setPixelColor(i, pixel); - #else - leds[i] = pixel; - #endif - - } + st.assignFrom(cmd); + #ifdef ADAFRUIT_LED + leds->setPixelColor(i, leds->Color(st.param.r,st.param.g,st.param.b)); + #else + leds[i] = CRGB(st.param.r,st.param.g,st.param.b); + #endif } } //for + + + if (show) { #ifdef ADAFRUIT_LED @@ -189,12 +167,12 @@ CRGB pixel; return 1; } -int out_SPILed::Ctrl(short cmd, short n, int * Parameters, int suffixCode, char* subItem) +int out_SPILed::Ctrl(itemCmd cmd, int suffixCode, char* subItem) { int chActive = item->isActive(); bool toExecute = (chActive>0); -itemStore st; -if (cmd>0 && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it +itemCmd st(ST_HSV); +if (cmd.isCommand() && !suffixCode) suffixCode=S_CMD; //if some known command find, but w/o correct suffix - got it int from=0, to=numLeds-1; //All LEDs on the strip by default // retrive LEDs range from suffix @@ -203,7 +181,7 @@ if (subItem) // debugSerial<setVal(st.aslong); + //item->setVal(st.getInt()); + st.saveItem(item); item->SendStatus(SEND_COMMAND | SEND_PARAMETERS ); } - PixelCtrl(&st,0,from,to); + PixelCtrl(st,from,to); } return 1; case CMD_OFF: if (subItem) // LED range, not whole strip - PixelCtrl(&st,CMD_OFF,from,to); + PixelCtrl(st.Cmd(CMD_OFF),from,to); else { - st.v=0; - PixelCtrl(&st,0,from,to); + st.Percents(0); + PixelCtrl(st,from,to); item->SendStatus(SEND_COMMAND); // if (send) item->publishTopic(item->itemArr->name,"OFF","/cmd"); } diff --git a/lighthub/modules/out_spiled.h b/lighthub/modules/out_spiled.h index 240bdd1..0a8334d 100644 --- a/lighthub/modules/out_spiled.h +++ b/lighthub/modules/out_spiled.h @@ -21,8 +21,9 @@ public: int Status() override; int isActive() override; int getChanType() override; - int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override; - int PixelCtrl(itemStore *st, short cmd, int from =0 , int to = 1024, bool show = 1, bool rgb = 0); + //int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override; + int Ctrl(itemCmd cmd, int suffixCode=0, char* subItem=NULL) override; + int PixelCtrl(itemCmd cmd, int from =0 , int to = 1024, bool show = 1); int numLeds; int8_t pin; int ledsType; diff --git a/lighthub/statusled.cpp b/lighthub/statusled.cpp new file mode 100644 index 0000000..a11af27 --- /dev/null +++ b/lighthub/statusled.cpp @@ -0,0 +1,89 @@ +/* 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 "statusled.h" + + +statusLED::statusLED(uint8_t pattern) +{ +#if defined (STATUSLED) + pinMode(pinRED, OUTPUT); + pinMode(pinGREEN, OUTPUT); + pinMode(pinBLUE, OUTPUT); + set(pattern); + timestamp=millis()+ledDelayms; +#endif +} + +void statusLED::show (uint8_t pattern) +{ +#if defined (STATUSLED) + digitalWrite(pinRED,(pattern & ledRED)?HIGH:LOW ); + digitalWrite(pinGREEN,(pattern & ledGREEN)?HIGH:LOW); + digitalWrite(pinBLUE,(pattern & ledBLUE)?HIGH:LOW); +#endif +} + +void statusLED::set (uint8_t pattern) +{ +#if defined (STATUSLED) + short newStat = pattern & ledParams; + + if (newStat!=(curStat & ledParams)) + { + //if (!(curStat & ledHidden)) + show(pattern); + curStat=newStat | (curStat & ~ledParams); + } +#endif +} + +void statusLED::flash(uint8_t pattern) +{ +#if defined (STATUSLED) + show(pattern); + curStat|=ledFlash; +#endif +} + +void statusLED::poll() +{ +#if defined (STATUSLED) + if (curStat & ledFlash) + { + curStat&=~ledFlash; + show(curStat); + } +if (millis()>timestamp) + { + + if (curStat & ledFASTBLINK) timestamp=millis()+ledFastDelayms; + else timestamp=millis()+ledDelayms; + + if (( curStat & ledBLINK) || (curStat & ledFASTBLINK)) + { + curStat^=ledHidden; + if (curStat & ledHidden) + show(0); + else show(curStat); + } + } +#endif +} diff --git a/lighthub/statusled.h b/lighthub/statusled.h new file mode 100644 index 0000000..159b520 --- /dev/null +++ b/lighthub/statusled.h @@ -0,0 +1,50 @@ +/* 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 + +*/ +#pragma once +#include + +#define ledRED 1 +#define ledGREEN 2 +#define ledBLUE 4 +#define ledBLINK 8 +#define ledFASTBLINK 16 +#define ledParams (ledRED | ledGREEN | ledBLUE | ledBLINK | ledFASTBLINK) + +#define ledFlash 32 +#define ledHidden 64 + +#define pinRED 50 +#define pinGREEN 51 +#define pinBLUE 52 + +#define ledDelayms 1000UL +#define ledFastDelayms 300UL + +class statusLED { +public: + statusLED(uint8_t pattern = 0); + void set (uint8_t pattern); + void show (uint8_t pattern); + void poll(); + void flash(uint8_t pattern); +private: + uint8_t curStat; + uint32_t timestamp; +}; diff --git a/lighthub/streamlog.cpp b/lighthub/streamlog.cpp index 1c4d40b..c045e7e 100644 --- a/lighthub/streamlog.cpp +++ b/lighthub/streamlog.cpp @@ -1,6 +1,6 @@ #include "streamlog.h" #include -#include "utils.h" +#include "statusled.h" #if defined (STATUSLED) extern statusLED LED; diff --git a/lighthub/textconst.h b/lighthub/textconst.h index bfe3a9e..8ae023f 100644 --- a/lighthub/textconst.h +++ b/lighthub/textconst.h @@ -100,7 +100,7 @@ const char DRY_P[] PROGMEM = "DRY"; const char HIGH_P[] PROGMEM = "HIGH"; const char MED_P[] PROGMEM = "MEDIUM"; const char LOW_P[] PROGMEM = "LOW"; - +const char ERROR_P[] PROGMEM = "ERR"; // SubTopics @@ -110,7 +110,7 @@ const char MODE_P[] PROGMEM = "mode"; const char FAN_P[] PROGMEM = "fan"; const char HUE_P[] PROGMEM = "hue"; const char SAT_P[] PROGMEM = "sat"; - +const char TEMP_P[] PROGMEM = "temp"; const char HSV_P[] PROGMEM = "HSV"; const char RGB_P[] PROGMEM = "RGB"; diff --git a/lighthub/utils.cpp b/lighthub/utils.cpp index 1c75adb..d710a19 100644 --- a/lighthub/utils.cpp +++ b/lighthub/utils.cpp @@ -630,73 +630,6 @@ itemCmd mapInt(int32_t arg, aJsonObject* map) return _itemCmd.Int(arg); } -statusLED::statusLED(uint8_t pattern) -{ -#if defined (STATUSLED) - pinMode(pinRED, OUTPUT); - pinMode(pinGREEN, OUTPUT); - pinMode(pinBLUE, OUTPUT); - set(pattern); - timestamp=millis()+ledDelayms; -#endif -} - -void statusLED::show (uint8_t pattern) -{ -#if defined (STATUSLED) - digitalWrite(pinRED,(pattern & ledRED)?HIGH:LOW ); - digitalWrite(pinGREEN,(pattern & ledGREEN)?HIGH:LOW); - digitalWrite(pinBLUE,(pattern & ledBLUE)?HIGH:LOW); -#endif -} - -void statusLED::set (uint8_t pattern) -{ -#if defined (STATUSLED) - short newStat = pattern & ledParams; - - if (newStat!=(curStat & ledParams)) - { - //if (!(curStat & ledHidden)) - show(pattern); - curStat=newStat | (curStat & ~ledParams); - } -#endif -} - -void statusLED::flash(uint8_t pattern) -{ -#if defined (STATUSLED) - show(pattern); - curStat|=ledFlash; -#endif -} - -void statusLED::poll() -{ -#if defined (STATUSLED) - if (curStat & ledFlash) - { - curStat&=~ledFlash; - show(curStat); - } -if (millis()>timestamp) - { - - if (curStat & ledFASTBLINK) timestamp=millis()+ledFastDelayms; - else timestamp=millis()+ledDelayms; - - if (( curStat & ledBLINK) || (curStat & ledFASTBLINK)) - { - curStat^=ledHidden; - if (curStat & ledHidden) - show(0); - else show(curStat); - } - } -#endif -} - #pragma message(VAR_NAME_VALUE(debugSerial)) #pragma message(VAR_NAME_VALUE(SERIAL_BAUD)) diff --git a/lighthub/utils.h b/lighthub/utils.h index bc45745..3fdc4cf 100644 --- a/lighthub/utils.h +++ b/lighthub/utils.h @@ -65,32 +65,3 @@ bool isTimeOver(uint32_t timestamp, uint32_t currTime, uint32_t time, uint32_t m bool executeCommand(aJsonObject* cmd, int8_t toggle = -1); bool executeCommand(aJsonObject* cmd, int8_t toggle, itemCmd _itemCmd); itemCmd mapInt(int32_t arg, aJsonObject* map); - -#define ledRED 1 -#define ledGREEN 2 -#define ledBLUE 4 -#define ledBLINK 8 -#define ledFASTBLINK 16 -#define ledParams (ledRED | ledGREEN | ledBLUE | ledBLINK | ledFASTBLINK) - -#define ledFlash 32 -#define ledHidden 64 - -#define pinRED 50 -#define pinGREEN 51 -#define pinBLUE 52 - -#define ledDelayms 1000UL -#define ledFastDelayms 300UL - -class statusLED { -public: - statusLED(uint8_t pattern = 0); - void set (uint8_t pattern); - void show (uint8_t pattern); - void poll(); - void flash(uint8_t pattern); -private: - uint8_t curStat; - uint32_t timestamp; -}; diff --git a/platformio.ini b/platformio.ini index 155e30d..7246a53 100644 --- a/platformio.ini +++ b/platformio.ini @@ -23,13 +23,13 @@ default_envs = ; mega2560-5500 ; LightHub controller HW revision 2.1 and above (Wiznet 5500 CS on pin 53) - lighthub21 +; lighthub21 ; Arduino DUE + Ethernet shield Wiznet 5100 ; due-5100 ; Generic DUE -; due + due ; Arduino DUE + Ethernet shield Wiznet 5500 ; due-5500 @@ -280,8 +280,8 @@ build_flags = !python get_build_flags.py due ; Need to place arduinoOTA utility from Arduino IDE distribution to folder in your PATH ;fix address and password ;upload_flags = -;upload_command = arduinoOTA -address 192.168.88.21 -port 65280 -username arduino -password password -b -upload /sketch -sketch $SOURCE -;upload_protocol = custom +upload_command = arduinoOTA -address 192.168.88.21 -port 65280 -username arduino -password password -b -upload /sketch -sketch $SOURCE +upload_protocol = custom lib_ignore = ;DS2482_OneWire //UNCOMMENT for software 1-wire driver DHT sensor library for ESPx