code: add EN Doxygen comments to core headers and drivers

Comments only, no logic changes: class/method/struct/enum documentation for
the item core, inputs, config/main, CAN, 1-Wire, Modbus, DMX and all
channel drivers (JSON config format, handled commands, published topics,
states). Verified with a clean 'due' build.
This commit is contained in:
2026-10-03 00:41:24 +03:00
parent b548474cd5
commit 8ea105cf42
32 changed files with 2113 additions and 110 deletions
+32
View File
@@ -1,26 +1,58 @@
#pragma once
#include "Arduino.h"
/**
* Channel status codes:
* - CST_UNKNOWN - not initialized yet
* - CST_FAILED - initialization failed
* - CST_INITIALIZED - hardware ready
* - CST_USER - user-defined state
*/
#define CST_UNKNOWN 0
#define CST_FAILED 1
#define CST_INITIALIZED 2
#define CST_USER 3
/**
* @brief Base class of all abstract channels (input and output drivers).
*
* abstractCh defines the lifecycle contract of a driver module:
* Setup() initializes the hardware and reserves resources, Poll() is
* called periodically with a cause (POLLING_SLOW/POLLING_FAST/...),
* Status()/setStatus() track the initialization state (CST_*) and Stop()
* releases the resources. The class also provides protected publishTopic()
* helpers that publish a value to a fixed MQTT topic as a retained message.
*/
class abstractCh {
public:
abstractCh(){};
virtual ~abstractCh(){};
/**
* Periodic poll hook.
* @param cause - POLLING_SLOW/POLLING_FAST/POLLING_INT/POLLING_1S
*/
virtual int Poll(short cause) {return 0;}
/** Initialize hardware and reserve resources. @return 0 on success. */
virtual int Setup() =0; //Should initialize hardware and reserve resources
// virtual int Anounce () {return 0;};
/** Free resources allocated in Setup(). */
virtual int Stop() {return 0;}; //Should free resources
/** @return current CST_* status of the channel. */
virtual int Status() {return CST_UNKNOWN;}
/** Set the CST_* status of the channel. */
virtual void setStatus(uint8_t status) {}
protected:
/**
* Publish an integer value to a topic as a retained MQTT message.
* @param topic - full topic or a name to be prefixed with the T_OUT topic
* @param subtopic - optional subtopic/suffix appended to the topic
*/
int publishTopic(const char* topic, long value, const char* subtopic = NULL);
/** Publish a float value to a topic as a retained MQTT message. */
int publishTopic(const char* topic, float value, const char* subtopic = NULL );
/** Publish a string value to a topic as a retained MQTT message. */
int publishTopic(const char* topic, const char * value, const char* subtopic = NULL, const char* suffix = NULL);
};
+12
View File
@@ -5,14 +5,26 @@
#include "abstractch.h"
class Input;
/**
* @brief Base class of input driver modules (sensors, encoders, ...).
*
* abstractIn extends abstractCh for drivers attached to an Input. The `in`
* pointer references the Input's config entry; the protected publish()
* helpers convert the measured value to a string and publish it to the
* MQTT topic given by the "emit" field of the input.
*/
class abstractIn : public abstractCh{
public:
abstractIn(Input * _in):abstractCh(){in=_in;};
protected:
/// Pointer to the Input (config entry) this driver is attached to
Input * in;
/** Publish an integer reading to the input's "emit" topic (retained). */
int publish(long value, const char* subtopic = NULL);
/** Publish a float reading to the input's "emit" topic (retained). */
int publish(float value, const char* subtopic = NULL );
/** Publish a string reading to the input's "emit" topic (retained). */
int publish(char * value, const char* subtopic = NULL);
friend Input;
};
+29
View File
@@ -4,25 +4,54 @@
#include "itemCmd.h"
class Item;
/** @brief Placeholder for driver-persistent data attached to an item (I_EXT field). */
class chPersistent {};
/**
* @brief Base class of output driver modules (one per channel type).
*
* abstractOut is the interface between the item core and a concrete device
* driver (out_dmx, out_relay, out_Modbus, ...). The driver is created by
* driverFactory, linked to the current item via link(), controls the
* hardware in Ctrl() and reports activity via isActive(). Drivers are
* shared between items of the same channel type, so all state must be
* derived from the linked item rather than stored per-instance.
*/
class abstractOut : public abstractCh{
public:
//abstractOut(Item * _item):abstractCh(){item=_item;};
abstractOut():item(NULL){};
/** Attach (or detach with NULL) the item this driver currently operates on. */
virtual void link(Item * _item){item=_item;};
/**
* @brief Execute a command on the hardware. Pure virtual - driver-specific.
* @param cmd - command and/or value to execute
* @param subItem - subchannel for multi-channel drivers
* @param toExecute - false means bookkeeping only (e.g. channel already inactive)
* @param authorized - true if the caller is authorized
*/
virtual int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) =0;
/** @return channel activity: 1 ON, 0 OFF, <0 unknown (core falls back to stored command). */
virtual int isActive();
/** Veto a command (driver-specific restrictions). @return true if the command is allowed. */
virtual bool isAllowed(itemCmd cmd){return true;};
/** @return the value used when the channel is turned ON without an explicit value. */
virtual itemCmd getDefaultOnVal(){return itemCmd().Percents255(255);};
/** @return the channel type (CH_*) handled by this driver. */
virtual int getChanType(){return 0;}
// virtual int getDefaultStorageType(){return 0;} /// Remove?? Now getChanType used instead
/** @return current CST_* status of the driver. */
virtual int Status() override;
/** Set the CST_* status of the driver. */
virtual void setStatus(uint8_t status) override;
/** Driver-level setup wrapper (validates the linked item before Setup of hardware). */
int Setup() override;
/** @return the item currently linked to this driver, or NULL. */
Item * getItem() {return item;}
protected:
/** Publish an on/off state change to the MQTT topics of the linked item. */
int pubAction(bool state);
/// Item currently linked to this driver
Item * item;
};
+11
View File
@@ -1,5 +1,6 @@
#pragma once
#include <Arduino.h>
/// Default brightness curve: linear mapping of percent to 8-bit value.
#define BRIGHT_LINEAR
@@ -103,4 +104,14 @@ const uint8_t stepvar[] PROGMEM =
0xF6, 0xF9, 0xFC, 0xFF};
#endif
/**
* @brief Convert a 0..255 brightness percentage to an 8-bit output value.
*
* Uses the active brightness curve: a straight passthrough when
* BRIGHT_LINEAR is set, otherwise a perceptual (log/step) lookup table
* so low brightness steps feel smoother to the eye.
*
* @param percent255 Brightness percentage in 0..255.
* @return The 8-bit brightness value to apply to the output.
*/
uint8_t getBright255(uint8_t percent255);
+258 -34
View File
@@ -1,5 +1,7 @@
#pragma once
/** @brief Sub-item index meaning "no sub-item" (top of 6-bit field). */
#define NO_SUBITEM 63
/** @brief Flag in the sub-item field: value is a command code, not an index. */
#define SUBITEM_IS_COMMAND 0x20
#ifdef CANDRV
@@ -17,7 +19,20 @@
#include <streamlog.h>
//#include <config.h> NO!
/** @brief 6-byte MAC address used for device identification over CAN. */
typedef uint8_t macAddress[6];
/**
* @brief 29-bit extended CAN identifier layout for the LightHub protocol.
*
* Bit layout (29-bit extended ID):
* - bits 0..5 subItemId (6 bits)
* - bits 6..15 itemId (10 bits); together bits 0..15 form subjId
* - bits 16..23 deviceId (1 byte)
* - bits 24..27 payloadType (4 bits)
* - bit 28 status: 0 = request, 1 = response
* - bits 29.. reserve (must be 0)
*/
#pragma pack(push, 1)
typedef union
{
@@ -40,36 +55,38 @@ typedef union
};
} canid_t;
/** @brief Payload (protocol) types carried in the CAN ID payloadType field. */
enum payloadType
{ unknown=0,
itemCommand=1,
lookupMAC=2,
configFrame=3,
OTAFrame=4,
auth=5,
metric=6,
sysCmd=7,
rawPinCtrl=8
itemCommand=1, /**< Item command or status (itemCmd payload). */
lookupMAC=2, /**< MAC address lookup / device ID exchange. */
configFrame=3, /**< Fragment of the JSON config transfer. */
OTAFrame=4, /**< Fragment of an OTA firmware transfer. */
auth=5, /**< Authentication frame. */
metric=6, /**< Metric value (see metricType). */
sysCmd=7, /**< System command (see commandType). */
rawPinCtrl=8 /**< Raw GPIO control of the remote controller. */
};
/** @brief Metric identifiers used in the metric payloadType frames. */
enum metricType
{
MAC=1,
IP=2,
NetMask=3,
GW=4,
DNS=5,
UpTime=6,
Salt=7
MAC=1, /**< MAC address. */
IP=2, /**< IP address. */
NetMask=3, /**< Network mask. */
GW=4, /**< Default gateway. */
DNS=5, /**< DNS server. */
UpTime=6, /**< Uptime in seconds. */
Salt=7 /**< Random salt for authentication. */
};
/** @brief System commands accepted in the sysCmd payloadType frames. */
enum commandType
{
reboot=1,
get=2,
save=3,
load=4
reboot=1, /**< Reboot the remote controller. */
get=2, /**< Fetch the remote config/state. */
save=3, /**< Persist the current config on the remote. */
load=4 /**< Reload the config on the remote. */
};
@@ -79,6 +96,12 @@ enum commandType
/**
* @brief 8-byte CAN payload, interpreted per payloadType.
*
* Overlays the possible frame bodies: item command + parameter,
* MAC + config CRC, system command + data, or two 32-bit metrics.
*/
typedef union {
uint8_t data[8];
char payload[8];
@@ -100,56 +123,184 @@ typedef union {
};
} datagram_t;
/** @brief States of the canDriver / canStream state machine. */
enum canState
{
stateUnknown,
MACLookup,
Idle,
StreamOpenedWrite,
StreamOpenedRead,
FrameRequested,
FrameReceived,
ReadConfig,
ConfigLoaded,
waitingConfirm,
Error
stateUnknown, /**< Not yet initialized. */
MACLookup, /**< Waiting for a lookupMAC response. */
Idle, /**< Idle, controller ID known. */
StreamOpenedWrite, /**< canStream open for writing. */
StreamOpenedRead, /**< canStream open for reading. */
FrameRequested, /**< A frame was requested, awaiting it. */
FrameReceived, /**< Requested frame arrived. */
ReadConfig, /**< Reading the remote JSON config. */
ConfigLoaded, /**< Remote config loaded successfully. */
waitingConfirm, /**< Write frame sent, awaiting confirmation. */
Error /**< Error state (retry / recoverable). */
};
#pragma pack(pop)
/**
* @brief CAN bus driver for the LightHub distributed-control protocol.
*
* Drives a 29-bit extended-ID CAN bus at 50 kbit/s and implements the
* LightHub CAN protocol: device discovery (MAC lookup to resolve the
* local controllerId), item command/status exchange, JSON config
* transfer, metrics and system commands. It keeps the local and remote
* CAN configuration objects from the JSON config and runs a small state
* machine (see canState) advanced by Poll(). A single instance (LHCAN)
* is used by the whole controller.
*/
class canDriver
{
public:
/** @brief Default constructor: driver not ready, unknown state. */
canDriver(){ready=false; controllerId=0; responseTimer=0; state=canState::stateUnknown;canConfigObj=NULL;canRemoteConfigObj=NULL;confCRC=0xFFFF;};
/**
* @brief Returns the local controller ID from the CAN config ("addr").
* @return Controller ID, or 0 if not configured.
*/
uint8_t getMyId();
/**
* @brief Sends the status of a local item to the local controller ID.
* @param itemNum Item number to report.
* @param cmd Command/parameters to report.
* @param subItem Sub-item index, or NO_SUBITEM.
* @return true if the frame was sent.
*/
bool sendStatus(uint16_t itemNum, itemCmd cmd, int subItem = NO_SUBITEM);
/**
* @brief Sends an item command (or status) to a remote CAN device.
* @param devID Target device ID.
* @param itemID Target item ID.
* @param cmd Command/parameters to send.
* @param status true for a status report, false for a command.
* @param subItemID Sub-item index, or NO_SUBITEM.
* @return true if the frame was sent.
*/
bool sendCommand(uint8_t devID, uint16_t itemID, itemCmd cmd, bool status=false, int subItemID=NO_SUBITEM );
/**
* @brief Sends a command described by a config array
* [devID, itemID, suffix, subItem].
* @param can Config array describing the target and sub-item.
* @param cmd Command/parameters to send.
* @param status true for a status report, false for a command.
* @return true if the frame was sent.
*/
bool sendCommand(aJsonObject * can,itemCmd cmd, bool status = false);
/**
* @brief Broadcasts the uptime metric over the CAN bus.
* @param ut Uptime value to send.
* @return true if the frame was sent.
*/
bool upTime(uint32_t ut);
/**
* @brief Broadcasts the salt metric over the CAN bus.
* @param salt Salt value to send.
* @return true if the frame was sent.
*/
bool salt(uint32_t salt);
/**
* @brief Broadcasts a lookupMAC request with the local MAC and waits
* for a response carrying the controller ID.
* @return true if the request frame was sent.
*/
bool lookupMAC();
/**
* @brief Requests a frame of the given type/sequence from a device.
* @param devId Target device ID.
* @param _payloadType Payload type to request.
* @param seqNo Frame sequence number.
* @return true if the request frame was sent.
*/
bool requestFrame(uint8_t devId, payloadType _payloadType, uint16_t seqNo );
/**
* @brief Reads one frame from the CAN bus into RXid/RXpacket/RXlen.
* @return Frame length in bytes, or -1 if no frame is available.
*/
int readFrame();
/**
* @brief Responds to a lookupMAC request with the device ID found by MAC.
* @param mac MAC address of the queried device.
* @return true if the response frame was sent.
*/
bool sendRemoteID(macAddress mac);
/**
* @brief Initializes the CAN hardware (50 kbit/s, extended IDs) and
* loads the local/remote CAN config objects from the JSON root.
* @return true on success.
*/
bool begin();
/**
* @brief One driver iteration: reads pending frames, processes them and
* advances the state machine (timeouts, config load).
*/
void Poll();
/**
* @brief Processes one received CAN frame (request or response).
* @param id Frame identifier.
* @param packet Pointer to the received payload.
* @param len Payload length in bytes.
* @param rtr true if it was a remote-transmission request.
* @return true if the frame was handled.
*/
bool processPacket(canid_t id, datagram_t *packet, uint8_t len, bool rtr=false);
/**
* @brief Writes one extended-ID frame to the CAN bus.
* @param msg_id 29-bit extended identifier.
* @param buf Payload to send, or NULL for a dataless frame.
* @param size Payload length (max 8).
* @return true if the frame was sent.
*/
bool write(uint32_t msg_id, datagram_t * buf = NULL, uint8_t size=0);
/**
* @brief Finds a remote device config object by its "name".
* @param devName Device name to search for.
* @param devAddr Optional output for the device address.
* @return Config object pointer, or NULL if not found.
*/
aJsonObject * findConfbyName(char* devName, int * devAddr=NULL);
#if not defined (NOIP)
/**
* @brief Subscribes to the MQTT topics of all remote CAN devices
* named in the remote config.
* @param root Topic prefix buffer to extend.
* @param buflen Length of the buffer.
* @return true on success.
*/
bool subscribeTopics(char * root, size_t buflen);
#endif
/**
* @brief Returns the local controller ID.
* @return Controller ID assigned by the CAN gateway.
*/
uint8_t getControllerID(){return controllerId;};
/**
* @brief Resolves a device ID from its MAC address in the remote config.
* @param mac MAC address to look up.
* @return Device ID, or 0 if not found.
*/
uint8_t getIdByMac(macAddress mac);
/** @brief Local CAN section of the JSON config ("can"). */
aJsonObject * canConfigObj;
/** @brief Remote controllers' CAN configs ("can.conf"). */
aJsonObject * canRemoteConfigObj;
/** @brief CRC16 of the current JSON config. */
uint16_t confCRC;
/** @brief Payload of the last received frame. */
datagram_t RXpacket;
/** @brief Identifier of the last received frame. */
canid_t RXid;
/** @brief Length of the last received frame. */
uint8_t RXlen;
private:
/**
* @brief Finds a remote device config object by its device ID.
* @param devId Device ID to look up.
* @return Config object pointer, or NULL if not found.
*/
aJsonObject * getConfbyID(uint8_t devId);
#if defined(ARDUINO_ARCH_STM32)
@@ -161,24 +312,48 @@ private:
//CAN_FRAME CAN_RX_msg;
#endif
/** @brief True once the CAN hardware is initialized. */
bool ready;
/** @brief Local controller ID resolved on the bus. */
uint8_t controllerId;
/** @brief Current driver/stream state machine state. */
canState state;
/** @brief millis() timestamp of the last response awaited. */
uint32_t responseTimer;
};
/** @brief MQTT topics tree shared with the CAN driver. */
extern aJsonObject * topics;
/**
* @brief Stream-over-CAN: reads/writes a byte stream as a sequence of
* 8-byte CAN frames to/from a remote controller.
*
* Used to transfer JSON config (and other payloads) reliably over the
* 50 kbit/s CAN bus. The sequence number starts at 0xFFFF (first frame)
* and then increments; writes buffer 8 bytes and wait for a
* confirmation, reads request frames and wait with timeout/retry.
* Behaves as an Arduino Stream (read/peek/available) and Print (write).
*/
class canStream : public Stream
{
public:
/**
* @brief Constructs a stream bound to the given driver.
* @param _driver Driver used to send/receive the frames.
*/
canStream(canDriver * _driver) : readPos(0),writePos(0),devId(0), pType(payloadType::unknown),state(canState::stateUnknown),seqNo(0),failedCount(0){driver=_driver; }
/**
* @brief Opens the stream to a remote controller in the given mode.
* @param controllerID Target device ID.
* @param _pType Payload type for the frames.
* @param _mode 'r' to read, 'w' to write.
* @return 1 on success.
*/
int open(uint8_t controllerID, payloadType _pType, char _mode)
{
if (mode) close();
@@ -191,6 +366,10 @@ public:
else state=canState::StreamOpenedRead;
return 1;
};
/**
* @brief Closes the stream, flushing any pending write data.
* @return 1 on success.
*/
int close ()
{
if ((mode == 'w') && writePos) flush();
@@ -201,31 +380,76 @@ public:
// Stream methods
/**
* @brief Number of bytes available for reading.
* @return Bytes ready in the current frame, or -1 on error.
*/
virtual int available();
/**
* @brief Reads one byte from the stream.
* @return Byte read, or -1 on error.
*/
virtual int read();
/**
* @brief Peeks at the next byte without consuming it.
* @return Next byte, or -1 on error.
*/
virtual int peek();
/**
* @brief Sends the partially filled write buffer as a frame.
*/
virtual void flush();
// Print methods
/**
* @brief Writes one byte; sends a frame when the buffer is full.
* @param c Byte to write.
* @return 1 on success, -1 on error.
*/
virtual size_t write(uint8_t c) ;
/**
* @brief Checks if the stream accepts writes right now.
* @return 1 if writable, 0 while waiting for confirmation.
*/
virtual int availableForWrite();
private:
/**
* @brief Sends one buffered frame with the given length/sequence.
* @param len Payload length to send (max 8).
* @param _seqNo Frame sequence number.
* @return 1 on success, 0 on failure.
*/
int send(uint8_t len, uint16_t _seqNo);
/**
* @brief Advances the stream state machine (request frames, wait
* for confirmations, time out).
* @return Bytes available for reading, or -1 if none yet.
*/
int checkState();
/** @brief Driver used for the underlying CAN I/O. */
canDriver * driver;
/** @brief Read offset within the current frame. */
unsigned int readPos;
/** @brief Write offset within the current frame buffer. */
unsigned int writePos;
/** @brief Write buffer for the in-progress frame. */
datagram_t writeBuffer;
/** @brief Target device ID. */
uint8_t devId;
/** @brief Current frame sequence number (starts at 0xFFFF). */
uint16_t seqNo;
/** @brief Consecutive frame failures before the stream errors out. */
int8_t failedCount;
/** @brief Access mode ('\0', 'r' or 'w'). */
char mode;
/** @brief Payload type of the frames. */
payloadType pType;
/** @brief Current stream state. */
canState state;
//bool writeBlocked;
};
+19
View File
@@ -6,19 +6,38 @@
#include <abstractout.h>
#include <item.h>
/**
* @brief Base class for multi-channel color output drivers (PWM, LED strip).
*
* Holds the base pin address and the number of configured channel
* addresses and implements the common color control logic; the actual
* output is delegated to the derived PixelCtrl().
*
* Color state is stored in HSV: hue 0..365, saturation 0..100,
* value 0..255, plus an optional color temperature (153..500 mireds)
* used by RGBWW channels to mix the two white channels.
*
* Control (Ctrl) suffixes: /set (percent or color value), /HSV,
* /cmd ON/OFF (ON restores the stored value, OFF clears it).
*/
class colorChannel : public abstractOut {
public:
/// Default constructor.
colorChannel():iaddr(0),numArgs(0) {};
/// Attach to an item; retrieve and store the base address and the number of channel addresses.
void link (Item * _item) {
abstractOut::link(_item);
iaddr = item->getArg(); //Once retrieve and store base address
if (iaddr<0) iaddr=-iaddr;
numArgs = item->getArgCount(); // and how many addresses is configured
};
/// Dispatch /set, /HSV and /cmd ON/OFF commands to PixelCtrl.
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized=false) override;
//int getDefaultStorageType()override;
/// Apply the color/brightness command to the output (implemented by the driver).
virtual int PixelCtrl(itemCmd cmd, char* subItem=NULL, bool show=true, bool authorized = false ) =0;
/// Get the pin address of the n-th channel (n=0 - base address).
short getChannelAddr(short n =0);
// int isActive() override;
protected:
+199 -4
View File
@@ -9,62 +9,257 @@
#include "systemconfigdata.h"
/**
* @brief Persistent system configuration manager.
*
* Reads and writes the fixed-layout system configuration block
* (see systemConfigData) stored in flash/EEPROM through a flashStream:
* MAC address, static network parameters, MQTT/OTA passwords, the remote
* configuration server URL and assorted control flags. It also keeps track
* of the ETag of the last downloaded remote config, which is sent back as
* "If-None-Match" so that the portal server can answer 304 (not modified)
* and skip the download. The full JSON config load/save/submit workflow
* (HTTP download from MY_CONFIG_SERVER / CONFIG_SERVER, CRC validation,
* save to flash) is driven from main.cpp via loadConfigFromHttp(),
* cmdFunctionSave() and cmdFunctionLoad().
*/
class systemConfig {
private:
/** @brief Flash (or SPIFFS) stream that backs the config storage. */
flashStream * stream;
/** @brief ETag of the currently stored remote config, in memory. */
String currentConfigETAG;
/**
* @brief Opens the backing stream in the given mode.
* @param mode Stream open mode ('r', 'w', ...).
* @return Stream open result.
*/
int openStream(char mode = '\0');
public:
/** @brief MAC address of the controller, kept in memory after load. */
macAddress mac;
/** @brief Constructs a config manager without a backing storage. */
systemConfig():currentConfigETAG() {stream=NULL;};
/**
* @brief Constructs a config manager over the given flash stream.
* @param fs Pointer to the flashStream holding the config block.
*/
systemConfig(flashStream * fs):currentConfigETAG() {stream=fs;};
/**
* @brief Checks that the stored config block has a valid signature.
* @return true if the "LHC3" signature in flash is intact.
*/
bool isValidSysConf();
/**
* @brief Loads the MAC address from flash into the mac member.
* @return true if a non-blank MAC was read.
*/
bool getMAC();
/**
* @brief Returns the MAC address of the controller as a hex string.
* @return MAC in "AA:BB:CC:DD:EE:FF" form.
*/
String getMACString();
/**
* @brief Stores the given MAC address into flash.
* @param mac MAC address to write.
* @return true on success.
*/
bool setMAC(macAddress& mac);
/**
* @brief Reads the MQTT password from flash into a caller buffer.
* @param buffer Output buffer for the password.
* @param bufLen Size of the buffer.
* @return Pointer to buffer if a password is stored, NULL otherwise.
*/
char * getMQTTpwd(char * buffer, uint16_t bufLen);
/**
* @brief Stores the MQTT password in flash (NULL clears it).
* @param pwd Null-terminated password, or NULL to erase.
* @return true on success.
*/
bool setMQTTpwd(char * pwd = NULL);
/**
* @brief Reads the OTA (HTTP update) password from flash.
* @param buffer Output buffer for the password.
* @param bufLen Size of the buffer.
* @return Pointer to buffer if a password is stored, NULL otherwise.
*/
char * getOTApwd(char * buffer, uint16_t bufLen);
/**
* @brief Stores the OTA password in flash (NULL clears it).
* @param pwd Null-terminated password, or NULL to erase.
* @return true on success.
*/
bool setOTApwd(char * pwd = NULL);
/**
* @brief Stores the URL of the remote configuration server in flash.
* Falls back to the compile-time MY_CONFIG_SERVER / CONFIG_SERVER
* when nothing is stored.
* @param url Null-terminated server URL.
* @return true on success.
*/
bool setServer(char* url);
/**
* @brief Reads the stored configuration server URL into a buffer.
* @param buffer Output buffer.
* @param bufLen Size of the buffer.
* @return Pointer to buffer if a URL is stored, NULL otherwise.
*/
char * getServer(char * buffer, uint16_t bufLen);
/**
* @brief Loads the static IP address from flash.
* @param ip Output IPAddress.
* @return true if a usable (non-blank) address was read.
*/
bool getIP(IPAddress& ip);
/**
* @brief Loads the static subnet mask from flash.
* @param mask Output IPAddress.
* @return true if a usable (non-blank) mask was read.
*/
bool getMask(IPAddress& mask);
/**
* @brief Loads the static DNS server address from flash.
* @param dns Output IPAddress.
* @return true if a usable (non-blank) address was read.
*/
bool getDNS(IPAddress& dns);
/**
* @brief Loads the static default gateway address from flash.
* @param gw Output IPAddress.
* @return true if a usable (non-blank) address was read.
*/
bool getGW(IPAddress& gw);
/**
* @brief Stores the static IP address in flash.
* @param ip Address to write.
* @return true on success.
*/
bool setIP(IPAddress& ip);
/**
* @brief Stores the static subnet mask in flash.
* @param mask Mask to write.
* @return true on success.
*/
bool setMask(IPAddress& mask);
/**
* @brief Stores the static DNS server address in flash.
* @param dns Address to write.
* @return true on success.
*/
bool setDNS(IPAddress& dns);
/**
* @brief Stores the static default gateway address in flash.
* @param gw Address to write.
* @return true on success.
*/
bool setGW(IPAddress& gw);
/**
* @brief Sets the serial (debug port) log level in flash.
* @param level Log level to store.
* @return true on success.
*/
bool setSerialDebuglevel(short);
/**
* @brief Sets the UDP (syslog) log level in flash.
* @param level Log level to store.
* @return true on success.
*/
bool setUdpDebuglevel(short);
/**
* @brief Returns the stored serial log level.
* @return Current serial log level.
*/
uint8_t getSerialDebuglevel();
/**
* @brief Returns the stored UDP log level.
* @return Current UDP log level.
*/
uint8_t getUdpDebuglevel();
/**
* @brief Wipes the whole config block and restores defaults.
* @return true on success.
*/
bool clear();
/**
* @brief Returns whether a successfully downloaded config is auto-saved.
* @return true if saving after HTTP load is enabled.
*/
bool getSaveSuccedConfig();
/**
* @brief Enables/disables auto-save of a successfully downloaded config.
* @param flag true to enable auto-save.
* @return true on success.
*/
bool setSaveSuccedConfig(bool);
/**
* @brief Returns whether the config is fetched from the portal over HTTP.
* @return true if HTTP config loading is enabled.
*/
bool getLoadHTTPConfig();
/**
* @brief Enables/disables fetching the config from the portal over HTTP
* (disabled permanently after a local config change).
* @param load true to allow HTTP config loading.
* @return true on success.
*/
bool setLoadHTTPConfig(bool);
/**
* @brief Returns the ETag of the current remote config, quoted for HTTP.
* @return ETag surrounded by double quotes (may be empty).
*/
String getETAG();
/**
* @brief Sets the in-memory ETag of the remote config (strips quotes).
* @param etag ETag string.
* @return true on success.
*/
bool setETAG(String etag);
/**
* @brief Persists the in-memory ETag to flash.
* @return true on success.
*/
bool saveETAG();
/**
* @brief Loads the stored ETag from flash into memory.
* @return true if a non-empty ETag was loaded.
*/
bool loadETAG();
/**
* @brief Returns whether static-IP-to-DHCP fallback is enabled.
* @return true if the controller may fall back to DHCP.
*/
bool getDHCPfallback();
/**
* @brief Enables/disables static-IP-to-DHCP fallback in flash.
* @param flag true to allow DHCP fallback.
* @return true on success.
*/
bool setDHCPfallback(bool flag);
/**
* @brief Returns the packed system configuration flags from flash.
* @return Current flags (log levels, behavior bits, config hash).
*/
systemConfigFlags getConfigFlags();
/**
* @brief Writes the packed system configuration flags to flash.
* @param flags Flags to store.
* @return true on success.
*/
bool setConfigFlags(systemConfigFlags flags);
//bool Save();
+56 -2
View File
@@ -16,7 +16,16 @@ Homepage: http://lazyhome.ru
GIT: https://github.com/anklimov/lighthub
e-mail anklimov@gmail.com
*/
*/
/**
* @file dmx.h
* @brief DMX input/output (and Art-Net) support.
*
* DMX output uses a hardware USART on the SAM3X8E (Due) and a software
* bit-banged driver (DmxSimple, pin 3/18) on AVR; DMX input uses
* DMXSerial on AVR and the hardware USART on the Due. Received DMX/Art-Net
* channels are mapped to items via the dmxArr configuration.
*/
#define D_UPDATED1 1
#define D_UPDATED2 2
#define D_UPDATED3 4
@@ -99,15 +108,60 @@ extern Artnet *artnet;
#include "aJSON.h"
/** @brief DMX channel-to-item mapping array from the JSON config. */
extern aJsonObject *dmxArr;
/**
* @brief Propagates received DMX input frames to the mapped items.
*
* DMX in/out is handled per platform: on the SAM3X8E (Due) a hardware
* USART (DmxDue) is used for both input and output; on AVR a software
* bit-banged driver (DmxSimple for output on pin 3/18, DMXSerial for
* input). ESP8266/ESP32 use ESPDMX.
*/
void DMXput(void);
/**
* @brief Sets up the DMX input for the given number of channels.
* @param channels Number of DMX input channels.
*/
void DMXinSetup(int channels);
/**
* @brief Sets up the DMX output for the given number of channels.
* @param channels Number of DMX output channels.
*/
void DMXoutSetup(int channels);
/**
* @brief Initializes the Art-Net receiver and wires its frame callback.
*/
void artnetSetup();
/**
* @brief Restricts the Art-Net channel range that is forwarded.
* @param minCh First channel (1-based) to accept.
* @param maxCh Last channel (1-based) to accept.
*/
void artnetSetChans(uint8_t minCh, uint8_t maxCh);
/**
* @brief DMX housekeeping called from the main loop: propagates changes
* and services pending DMX-in updates.
*/
void DMXCheck(void);
/**
* @brief Drives a named DMX/LED item with an RGB(W) value.
* @param name Name of the target item.
* @param r Red component.
* @param g Green component.
* @param b Blue component.
* @param w White component.
* @return 1 on success, 0 if the item was not found.
*/
int itemCtrl2(char* name,int r,int g, int b, int w);
/**
* @brief Writes a value into the intermediate (smoothing) DMX output buffer.
* @param chan 1-based DMX channel.
* @param val Channel value 0..255.
*/
void DmxWriteBuf(uint16_t chan,uint8_t val);
/**
* @brief Smoothly ramps the actual DMX output toward the buffered target.
*/
void DMXOUT_propagate();
+123 -6
View File
@@ -17,6 +17,17 @@ GIT: https://github.com/anklimov/lighthub
e-mail anklimov@gmail.com
*/
/**
* @file inputs.h
* @brief LightHub input system (digital/analog pins, sensors, encoders).
*
* Every physical input (button, switch, analog pin, rotary encoder,
* DHT22/CCS811/HDC1080 sensor, counter, uptime) is an #Input entry of the
* global #inputs JSON object. An input debounces its signal, runs an IS_*
* state machine and executes the actions configured in the "act" field
* (commands to items / emit topics). The runtime state of each input is
* packed into the #inStore union stored in the JSON object itself.
*/
#pragma once
#include <aJSON.h>
#include "modules/in_ccs811_hdc1080.h"
@@ -25,6 +36,23 @@ e-mail anklimov@gmail.com
#include "RotaryEncoder.h"
#endif
/**
* Input type flags (bitmask, combined with "|"):
* - IN_PUSH_ON (0) - button: ON while pressed, OFF on release (overridden by scmd/rcmd) - default
* - IN_PUSH_TOGGLE (1) - every physical push toggles the logical switch on/off (toggle on leading edge)
* - IN_ACTIVE_HIGH (2) - high level = PUSHED/CLOSED/ON, otherwise low; use INPUT mode instead of INPUT_PULLUP
* - IN_RE (32) - rotary encoder input
* - IN_ANALOG (64) - analog input
* - IN_I2C (128) - pin on an MCP23017 I2C expander
*
* Sensor types (no GPIO edge, polled in the CHECK_SENSOR cycle):
* - IN_DHT22 (4), IN_CCS811 (5), IN_HDC1080 (6), IN_ULTRASONIC (7),
* IN_COUNTER (8), IN_UPTIME (16).
*
* Examples: 0 - plain button (default), 1 - toggle button, 2 - active-high
* switch, 64 - analog input, 66 - active-high analog input,
* 32 (IN_RE) combined with 0/1 - rotary encoder, 128 - I2C expander button.
*/
#define IN_ACTIVE_HIGH 2 // High level = PUSHED/ CLOSED/ ON othervise :Low Level. Use INPUT mode instead of INPUT_PULLUP for digital pin
#define IN_ANALOG 64 // Analog input
#define IN_RE 32 // Rotary Encoder (for further use)
@@ -33,14 +61,25 @@ e-mail anklimov@gmail.com
#define IN_PUSH_ON 0 // PUSH - ON, Release - OFF (ovverrided by pcmd/rcmd) - DEFAULT
#define IN_PUSH_TOGGLE 1 // Used for push buttons. Every physicall push toggle logical switch on/off. Toggle on leading edge
#define IN_DHT22 4
#define IN_CCS811 5
#define IN_HDC1080 6
#define IN_ULTRASONIC 7
#define IN_DHT22 4 // DHT22 temperature/humidity sensor
#define IN_CCS811 5 // CCS811 eCO2 sensor
#define IN_HDC1080 6 // HDC1080 temperature/humidity sensor
#define IN_ULTRASONIC 7 // Ultrasonic distance sensor
#define IN_COUNTER 8
#define IN_UPTIME 16
#define IN_COUNTER 8 // Hardware counter (IRQ) input
#define IN_UPTIME 16 // Publishes system uptime
/**
* Input state machine states (IS_*):
* - IS_IDLE - initial state, contact not touched yet
* - IS_PRESSED / IS_RELEASED - moment of press / release (commands fire here)
* - IS_LONG - long press detected (after T_LONG ms)
* - IS_REPEAT - auto-repeat while held (T_RPT period)
* - IS_WAITPRESS / IS_WAITRELEASE - waiting for the edge (delayed state)
* - IS_PRESSED2/3, IS_LONG2/3, IS_REPEAT2/3 - 2nd/3rd contact states (multi-pin inputs)
* - IS_REQSTATE - request: apply the stored reqState on the next poll
* - IS_NOP - no state change
*/
#define IS_IDLE 0
#define IS_PRESSED 1
#define IS_RELEASED 2
@@ -60,16 +99,26 @@ e-mail anklimov@gmail.com
/// Debounce: number of identical reads / analog samples before a state change
#define SAME_STATE_ATTEMPTS 2
#define ANALOG_STATE_ATTEMPTS 6
/// Analog noise threshold (ADC steps)
#define ANALOG_NOIZE 1
/**
* Poll causes (see Input::Poll()):
* - CHECK_SENSOR - slow cycle (sensors: DHT22, CCS811, counters, ...)
* - CHECK_INPUT - fast cycle (contacts, analog)
* - CHECK_INTERRUPT - realtime (ISR-driven)
* - CHECK_ULTRASONIC - ultrasonic ping
*/
#define CHECK_SENSOR 1
#define CHECK_INPUT 2
#define CHECK_INTERRUPT 3
#define CHECK_ULTRASONIC 4
/** Timing parameters (ms): long press, idle, auto-repeat period, repeat pulse, max repeat. */
#define T_LONG 1000
#define T_IDLE 600
#define T_RPT 300
@@ -111,6 +160,13 @@ e-mail anklimov@gmail.com
extern aJsonObject *inputs;
/**
* @brief Per-input runtime state, packed into the JSON object's valueint.
*
* Digital layout: toggle flags of 3 contacts, last raw value, delayed-state
* flag, bounce counter, current/required state (IS_*) and a 16-bit timestamp.
* Analog layout: logic state plus a 16-bit current ADC value.
*/
typedef union {
uint32_t aslong;
uint32_t timestamp;
@@ -136,27 +192,58 @@ typedef union {
} inStore;
/**
* @brief A single LightHub input (digital/analog pin, sensor, encoder, ...).
*
* Input reads one physical input, debounces it, runs the IS_* state machine
* and executes the actions (commands) configured in the "act" field of the
* input's JSON object. Supported types are defined by the IN_* flags:
* buttons, switches, analog inputs, rotary encoders, DHT22/CCS811/HDC1080
* sensors, hardware counters and uptime.
*/
class Input {
public:
/// Pointer to the input's JSON object in the global inputs tree
aJsonObject *inputObj;
/// Input type (IN_* flags)
uint8_t inType;
/// First pin number
uint8_t pin;
/// Second pin number (rotary encoder, ...)
uint8_t pin2;
/// Runtime state, packed in the JSON object's valueint
inStore *store;
/**
* @brief Create an input from its JSON object.
* @param obj - the input object (or a nested "act" sub-object)
* @param configObj - object holding the "T"/"#" fields (defaults to obj)
*/
Input(aJsonObject *obj, aJsonObject * configObj = NULL);
/** @brief Create an input by name from the global inputs object. */
Input(char *name);
/** @return true if the input was parsed (store != NULL). */
boolean isValid();
/** ISR entry: raw digital edge; the state change is applied on the next Poll(). */
void onContactChanged(int newValue);
/** ISR entry for an analog/encoder change carrying the new value. */
void onAnalogChanged(itemCmd newValue);
/**
* Poll the input by cause: CHECK_INPUT/CHECK_INTERRUPT for contacts,
* CHECK_SENSOR for sensors, CHECK_ULTRASONIC for ultrasonic.
*/
int Poll(short cause);
/** Initialize pin modes / sensor drivers. */
void setup();
/** Release resources (e.g. delete the rotary encoder instance). */
void stop();
/** ISR handler: increment a hardware counter value. */
static void inline onCounterChanged(int i);
/** ISR handlers for counter interrupt numbers 0..5. */
static void onCounterChanged0();
static void onCounterChanged1();
static void onCounterChanged2();
@@ -167,40 +254,61 @@ public:
protected:
/** Parse the "T" (type) and "#" (pin / [pin, pin2]) fields of the input object. */
void Parse(aJsonObject * configObj = NULL);
/** Debounce the contact and advance the IS_* state machine; fire actions on state change. */
#ifdef ROTARYENCODER
void contactPoll(short cause, RotaryEncoder * re = NULL);
#else
void contactPoll(short cause);
#endif
/** Sample the analog value with noise filtering; fire actions on change. */
void analogPoll(short cause);
/** Read the DHT22 temperature/humidity, execute the temp/hum actions and emit. */
void dht22Poll();
/** Publish the hardware counter value to the emit topic. */
void counterPoll();
/** Attach a rising-edge ISR to a counter pin. */
void attachInterruptPinIrq(int realPin, int irq);
/** @return the timestamp of the next allowed poll for this pin. */
unsigned long nextPollTime() const;
/** Set the timestamp of the next allowed poll for this pin. */
void setNextPollTime(unsigned long pollTime);
/** Publish the system uptime (millis) to the emit topic. */
void uptimePoll();
/** Publish a sensor value in the Domoticz JSON format to the emit topic. */
bool publishDataToDomoticz(int , aJsonObject *, const char *format, ...);
/** @return the Domoticz "idx" field of the input, or NULL. */
char* getIdxField();
/**
* Apply a new IS_* state: debouncing, delayed-state handling and
* execution of the actions configured for the state.
*/
bool changeState(uint8_t newState, short cause, aJsonObject * currentInputObject, bool contactState, bool calledOnTimer = false);
/** Create the RotaryEncoder instance for an IN_RE input. */
void setupRotaryEncoder();
/** @return the currently active sub-input object of an "act" group (or the input itself). */
aJsonObject * getCurrentInput();
/** Select the active sub-input of an "act" group by object. @return true on success. */
bool setCurrentInput(aJsonObject * obj);
/** Select the active sub-input of an "act" group by 1-based index. @return true on success. */
bool setCurrentInput(int n);
/** Select the active sub-input of an "act" group by name. @return true on success. */
bool setCurrentInput(char * name);
/** Process the "activate" instruction: switch to another input. @return true if handled. */
bool checkInstructions(aJsonObject * obj);
//bool executeCommand(aJsonObject* cmd, int8_t toggle = -1, char* defCmd = NULL);
@@ -208,17 +316,26 @@ protected:
/**
* @brief Cache of raw pin reads to avoid multiple GPIO/I2C accesses
* within one poll cycle.
*/
class readCache {
public:
readCache();
/** Cached analogRead (with an optional trigger pin, e.g. for ultrasonic). */
uint16_t analogReadCached (uint8_t pin, uint8_t trigPin=0, uint8_t _type = IN_ANALOG);
/** Cached digitalRead. */
uint8_t digitalReadCached(uint8_t pin);
#ifdef MCP23017
/** Cached MCP23017 bit read. */
uint8_t I2CReadBit(uint8_t type, uint8_t addr, uint8_t pin);
#endif
/** Invalidate the cached read so the next read hits the hardware. */
void invalidateInputCache();
protected:
/// Cached I2C address / type / value
uint8_t addr;
uint8_t type;
uint16_t cached_data;
+44
View File
@@ -1,3 +1,13 @@
/**
* @file ipmodbus.h
* @brief IP<->UART Modbus gateway (Modbus TCP/UDP over RS485).
*
* Bridges Modbus TCP/UDP clients (port 502) to a Modbus RTU device on a
* UART (RS485). Incoming TCP/UDP requests are queued, sent serially with
* inter-frame delays, and the RTU responses are matched back to the
* originating client by MBAP transaction/unit ID. Based on the
* arduino-modbus-rtu-tcp-gateway design.
*/
#ifdef IPMODBUS
#pragma once
#include <CircularBuffer.h> // CircularBuffer https://github.com/rlogiacco/CircularBuffer
@@ -26,6 +36,10 @@ const int serialTimeout = 800;
const int tcpPort = 502;
const int udpPort = 502;
/**
* @brief Metadata for one queued Modbus request, so the RTU response
* can be routed back to the correct client.
*/
typedef struct {
byte tid[2]; // MBAP Transaction ID
byte uid; // MBAP Unit ID (address)
@@ -75,6 +89,7 @@ const int modbusSize = 256; // size of a MODBUS RTU frame (determines s
/** @brief States of the serial (RTU) transmission sub-state machine. */
enum state : byte
{
MBIDLE, SENDING, DELAY, WAITING
@@ -82,10 +97,39 @@ enum state : byte
/**
* @brief Main gateway loop: receives TCP/UDP requests, processes the
* queue and drives the serial RTU transmission.
*/
void ipmodbusLoop();
/**
* @brief Initializes the Ethernet server/UDP and the Modbus UART.
*/
void setupIpmodbus();
/**
* @brief Validates an incoming Modbus TCP/UDP request (MBAP header, and
* RTU CRC when RTU-over-TCP is used).
* @param buffer Received frame.
* @param bufferSize Frame length.
* @return 0 if the request is valid, otherwise an error code.
*/
byte checkRequest(byte buffer[], unsigned int bufferSize);
/**
* @brief Updates the running Modbus RTU CRC with one byte.
* @param b Byte to add to the CRC.
*/
void calculateCRC(byte b);
/**
* @brief Checks whether a Modbus slave (by address) is currently responding.
* @param index Slave address.
* @return true if the slave is marked as responding.
*/
bool getSlaveResponding(const uint8_t index);
/**
* @brief Verifies the trailing Modbus RTU CRC of a frame.
* @param buf Frame including the CRC bytes.
* @param len Frame length.
* @return true if the CRC is valid.
*/
bool checkCRC(byte buf[], int len);
#endif
+248
View File
@@ -17,12 +17,32 @@ GIT: https://github.com/anklimov/lighthub
e-mail anklimov@gmail.com
*/
/**
* @file item.h
* @brief Item (virtual channel) system of LightHub.
*
* LightHub models every controllable device, sensor or service as an "item"
* (virtual channel) stored as a JSON array inside the global #items object:
* `[type, arg, val, cmd, ext, timestamp]` (element indexes I_TYPE..I_TIMESTAMP).
* The item name is the key of the array; the type (CH_*) selects the driver
* module (DMX dimmer, relay, Modbus, thermostat, AC, ...) that handles it.
*
* An item can be a single channel (CH_DIMMER, CH_RELAY, ...) or a CH_GROUP
* pseudo-channel which fans commands out recursively to its members.
* Commands and values travel to/from items as #itemCmd structures and are
* published to MQTT topics with the suffixes of suffix_P
* (cmd/set/val/del/HSV/RGB/fan/mode/ctrl/hue/sat/temp/raw).
*/
#pragma once
#include "options.h"
#include "abstractout.h"
#include "itemCmd.h"
/**
* Suffix (subtopic) codes - indexes of the suffix_P table below.
* S_NOTFOUND means "no suffix given" (universal/empty OpenHAB topic).
*/
#define S_NOTFOUND 0
#define S_CMD 1
#define S_SET 2
@@ -38,6 +58,23 @@ e-mail anklimov@gmail.com
#define S_TEMP 12
#define S_RAW 13
/**
* MQTT topic suffixes (subtopics) used per item:
* - "" - base item topic
* - "cmd" - current command (ON/OFF/...)
* - "set" - setpoint / target value
* - "val" - actual measured value
* - "del" - delayed (timed) command status, remaining time
* - "HSV" - HSV color notation for color channels
* - "RGB" - RGB color notation for color channels
* - "fan" - fan speed subchannel (AC/vent)
* - "mode" - device mode (AC modes)
* - "ctrl" - control flags (ENABLE/DISABLE/FREEZE)
* - "hue" - hue component only
* - "sat" - saturation component only
* - "temp" - color temperature component only
* - "raw" - raw (unparsed) payload passthrough
*/
typedef char suffixstr[5];
const suffixstr suffix_P[] PROGMEM =
{"","cmd","set","val","del","HSV","RGB","fan","mode","ctrl","hue","sat","temp","raw"};
@@ -45,6 +82,36 @@ const suffixstr suffix_P[] PROGMEM =
#define S_ADDITIONAL 13
/**
* Channel (item) types, indexes CH_DIMMER(0)..CH_SPRINKLER(23)=CH_MAX.
* The type is stored in the I_TYPE element of the item array and selects
* the output driver module that handles the item.
*
* - CH_DIMMER = 0 - DMX dimmer, 1-4 channels
* - CH_RGBW = 1 - DMX 4-channel RGB+W
* - CH_RGB = 2 - DMX 3-channel RGB
* - CH_PWM = 3 - PWM output directly to PIN, 1-4 CH
* - CH_MODBUS = 4 - Modbus AC dimmer
* - CH_THERMO = 5 - simple ON/OFF thermostat
* - CH_RELAY = 6 - ON/OFF relay output
* - CH_GROUP = 7 - group pseudo-channel (fan-out to members)
* - CH_VCTEMP = 8 - Vacom PID temperature regulator
* - CH_VC = 9 - Vacom Modbus fan (motor) regulator
* - CH_AC = 10 - Haier AC
* - CH_SPILED = 11 - SPI LED strip
* - CH_MOTOR = 12 - motorized gate with feedback resistor
* - CH_PID = 13 - generic PID regulator
* - CH_MBUS = 14 - universal Modbus master channel
* - CH_UARTBRIDGE = 15 - bridge between 2 UARTs (PDU reporting via UDP)
* - CH_RELAYX = 16 - slow PWM relay for objects with inertia
* - CH_RGBWW = 17 - DMX RGBWW (warm and cold white)
* - CH_MULTIVENT = 18 - multichannel ventilation
* - CH_ELEVATOR = 19 - elevator controller
* - CH_COUNTER = 20 - generic counter
* - CH_HUMIDIFIER = 21 - humidifier
* - CH_MERCURY = 22 - Mercury energy meter / RS485 interface
* - CH_SPRINKLER = 23 - sprinkler controller
*/
#define CH_DIMMER 0 //DMX 1-4 ch
#define CH_RGBW 1 //DMX 4 ch
#define CH_RGB 2 //DMX 3 ch
@@ -71,16 +138,39 @@ const suffixstr suffix_P[] PROGMEM =
#define CH_SPRINKLER 23
#define CH_MAX 23
/**
* Polling rates/causes passed to Item::Poll() and driver Poll():
* - POLLING_SLOW - periodic slow poll cycle
* - POLLING_FAST - fast poll loop
* - POLLING_INT - called from an interrupt context (ISR-safe actions only)
* - POLLING_1S - once per second tick
*/
#define POLLING_SLOW 1
#define POLLING_FAST 2
#define POLLING_INT 3
#define POLLING_1S 4
//CTRL Execution flags
/**
* Group execution flags passed to Item::Ctrl(itemCmd, ...):
* - CTRL_DISABLE_RECURSION - do not recurse into group members
* - CTRL_DISABLE_NON_GRP - skip non-group items
* - CTRL_SCHEDULED_CALL_RECURSION - both; used when a scheduled
* (delayed) group command fires back
*/
#define CTRL_DISABLE_RECURSION 1
#define CTRL_DISABLE_NON_GRP 2
#define CTRL_SCHEDULED_CALL_RECURSION (CTRL_DISABLE_RECURSION | CTRL_DISABLE_NON_GRP)
/**
* Element indexes of the item JSON array:
* - I_TYPE - channel type (CH_*)
* - I_ARG - channel-type depended argument (pin, address or argument array)
* - I_VAL - latest preset value (int, float or array of presets)
* - I_CMD - latest command received (CMD_* packed with FLAG_* bits)
* - I_EXT - channel-depended extension data
* - I_TIMESTAMP - armed delayed command (ms timestamp, subtype = CMD_*)
*/
#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)
@@ -88,12 +178,17 @@ const suffixstr suffix_P[] PROGMEM =
#define I_EXT 4 //Chanell-depended extension - array
#define I_TIMESTAMP 5
/**
* Argument array indexes of a CH_MODBUS item:
* I_ARG = [addr, reg, mask, maxScale, regType].
*/
#define MODBUS_CMD_ARG_ADDR 0
#define MODBUS_CMD_ARG_REG 1
#define MODBUS_CMD_ARG_MASK 2
#define MODBUS_CMD_ARG_MAX_SCALE 3
#define MODBUS_CMD_ARG_REG_TYPE 4
/** Modbus register types: 0 - coils, 1 - discrete inputs, 2 - holding, 3 - input. */
#define MODBUS_COIL_REG_TYPE 0
#define MODBUS_DISCRETE_REG_TYPE 1
#define MODBUS_HOLDING_REG_TYPE 2
@@ -101,93 +196,246 @@ const suffixstr suffix_P[] PROGMEM =
#include "aJSON.h"
/** Global JSON object with all items (virtual channels) of this device. */
extern aJsonObject *items;
/** Store the current measured temperature of a CH_THERMO item by name. */
extern short thermoSetCurTemp(char *name, float t);
/**
* Translate a text payload to a command code.
* @param payload - text command word ("ON", "HALT", ...), number or color
* @return CMD_* code; CMD_VOID for plain numbers, CMD_UNKNOWN if unrecognized
*/
int txt2cmd (char * payload);
/**
* Translate a subtopic text to a suffix code (index of suffix_P).
* @param payload - suffix text ("cmd", "set", "val", ...)
* @return suffix code (S_*) or S_NOTFOUND
*/
int txt2subItem(char *payload);
/**
* @brief A single LightHub item (virtual channel).
*
* Item wraps one entry of the global #items JSON object: a virtual channel
* of a given type (CH_*) with its arguments, current value, last command,
* optional extension data and a driver instance. Items are the core
* abstraction of the system: physical outputs (relays, DMX, Modbus, AC),
* groups and services are all items, addressed by name ("name",
* "name/subItem" or "name/subItem/suffix").
*
* All external control is funneled through the Ctrl() overloads; the
* resulting state is published to MQTT (and CAN when enabled) via
* SendStatus()/SendStatusImmediate().
*/
class Item
{
public:
/// Pointers into the items JSON tree (root object, item array, its arg/val/ext elements)
aJsonObject *rootItems, *itemArr, *itemArg,*itemVal,*itemExt;
/// Channel type (CH_*) resolved from I_TYPE
uint8_t itemType;
/// Linked output driver instance (shared per channel type) or NULL
abstractOut * driver;
/**
* @brief Create an item by name from the items object.
* @param name - item name, optionally "item/subItem" or "item/subItem/suffix";
* the tail sets the default subItem/suffix used by later Ctrl() calls
* @param _items - root items object (global #items by default)
*/
Item(char * name, aJsonObject *_items = items);
/**
* @brief Adopt an existing items-array element as this item.
* @param obj - pointer to the item array inside the items object
*/
Item(aJsonObject * obj, aJsonObject *_items = items);
/**
* @brief Create an item by CAN id (CANDRV builds).
* @param num - CAN identifier of the item
* @param subItem - default subItem id, optionally OR'ed with SUBITEM_IS_COMMAND
*/
Item(uint16_t num, uint8_t subItem, aJsonObject *_items = items);
/** Destructor: releases the driver if it is not shared with another item. */
~Item();
/** @return true if the item was found in config and is a valid array. */
boolean isValid ();
/**
* Initialize the driver hardware.
* @return true on success (driver present and started)
*/
boolean Setup();
/** Stop the driver and release its hardware resources. */
void Stop();
//int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL);
/**
* @brief Main control entry point: execute a command/value on this item.
* @param cmd - command and/or value (see #itemCmd)
* @param subItem - subchannel name or NULL (default subItem is used)
* @param flags - CTRL_* group execution flags
* @param authorized - true if the caller is authorized (unlocks DISABLE, etc.)
* @return 1 - executed, 3 - no action needed (target state reached),
* -1 - error, -4 - invalid argument, -5 - unauthorized, -6 - disabled
*/
int Ctrl(itemCmd cmd, char* subItem=NULL, uint8_t flags = 0, bool authorized=false);
/**
* Parse a text payload (command word, number, color, optional
* "token@command" authorization prefix) and route it to Ctrl(itemCmd, ...).
* @param remoteID - if non-zero, forward the command to that remote node via CAN
*/
int Ctrl(char * payload, char * subItem=NULL, int remoteID = 0);
/**
* Forward a command to a remote LightHub node over CAN.
* @param cmd - command to send, @param remoteID - target node id
* @param authToken - optional authorization token
*/
int remoteCtrl(itemCmd cmd, int remoteID, char* subItem=NULL, char * authToken=NULL);
/** @return n-th argument (I_ARG) as int; 0 if absent. */
int getArg(short n=0);
/** @return n-th argument (I_ARG) as float; 0.0 if absent. */
float getFloatArg(short n=0);
/** @return number of arguments in I_ARG (0, 1 or array size). */
short getArgCount();
//int getVal(short n); //From VAL array. Negative if no array
/** @return latest stored value (I_VAL) as int, from a scalar or first array element. */
long int getVal(); //From int val OR array
/** @return storage subtype (ST_*) of the stored value. */
uint8_t getSubtype();
/** @return last command code (CMD_*) stored in I_CMD, or -1. */
uint8_t getCmd();
/** @return channel extension data (I_EXT) as int. */
long int getExt(); //From int val OR array
/** Store an integer extension value in I_EXT. */
void setExt(long int par);
/** @return pointer to driver-persistent data attached to I_EXT, or NULL. */
chPersistent * getPersistent();
/** Attach driver-persistent data to I_EXT. @return the stored pointer. */
chPersistent * setPersistent(chPersistent * par);
/** Store a command code in I_CMD, preserving the FLAG_* bits. */
void setCmd(uint8_t cmdValue);
/** @return subset of stored FLAG_* bits matching `flag` (default: all). */
uint32_t getFlag (uint32_t flag=FLAG_MASK);
/** Set FLAG_* bits in I_CMD, preserving the CMD_* bits. */
void setFlag (uint32_t flag);
/** Clear FLAG_* bits in I_CMD, preserving the CMD_* bits. */
void clearFlag (uint32_t flag);
/** Store an integer value in I_VAL (only if I_VAL is a scalar). */
void setVal(long int par);
/** Store a float value in I_VAL (only if I_VAL is a scalar). */
void setFloatVal(float par);
/** Store the storage subtype (ST_*) of I_VAL. */
void setSubtype(uint8_t par);
/**
* Periodic poll of the item.
* Fires armed delayed (S_DELAYED) commands, retries failed transactions
* and delegates to the driver.
* @param cause - POLLING_SLOW/POLLING_FAST/POLLING_INT/POLLING_1S
*/
int Poll(int cause);
/**
* Publish current command/value/flags to MQTT.
* @param sendFlags - FLAG_COMMAND/FLAG_PARAMETERS/FLAG_FLAGS and
* FLAG_SEND_IMMEDIATE/FLAG_SEND_DEFFERED options
* Defers the publish (sets a pending flag) if the network is busy or RAM is low.
*/
int SendStatus(long sendFlags, char * subItem=NULL);
/** Publish the given status to MQTT immediately without deferral. */
int SendStatusImmediate(itemCmd st, long sendFlags, char * subItem=NULL, bool tetain = true);
/** @return 1 if the channel is active (ON), 0 if OFF, -1 if invalid. */
int isActive();
/** @return effective channel type (CH_*) - from the driver if present, else I_TYPE. */
int getChanType();
/** Turn the channel ON. */
inline int On (){return Ctrl(itemCmd(ST_VOID,CMD_ON));};
/** Turn the channel OFF. */
inline int Off(){return Ctrl(itemCmd(ST_VOID,CMD_OFF));};
/** Toggle the channel ON/OFF. */
inline int Toggle(){return Ctrl(itemCmd(ST_VOID,CMD_TOGGLE));};
/**
* Arm a delayed command: execute `cmd`'s command code after the delay
* stored in cmd parameter (ms). @param authorized - stored with the timer.
*/
int scheduleCommand(itemCmd cmd, bool authorized);
/**
* Arm the opposite command after the given delay (momentary action):
* e.g. CMD_ON -> CMD_OFF after N ms, CMD_XON -> CMD_XOFF, etc.
* @param isActiveNow - current activity state of the channel
*/
int scheduleOppositeCommand(itemCmd cmd,short isActiveNow,bool authorized);
/** @return the scheduled command code (CMD_*), 0 if nothing is armed. */
int isScheduled();
/** @return name of the subItem with the given id, or NULL. */
char * getSubItemStrById(uint8_t subItem);
/** @return id of the subItem with the given name, or NO_SUBITEM. */
uint8_t getSubitemId(char * subItem);
protected:
/**
* Recursively walk a group's member list.
* If cmd is given - execute it on every member (non-recursively);
* otherwise check members' activity and return true if any is active.
*/
bool digGroup (aJsonObject *itemArr, itemCmd *cmd = NULL, char* subItem = NULL, bool authorized = false, uint8_t ctrlFlags = 0);
/** @return maximum settable value for this channel type (255, 80 for thermostat, 0 for counter). */
long int limitSetValue();
/** (Legacy) Set Vacom fan speed over Modbus. */
int VacomSetFan (itemCmd st);
/** (Legacy) Set Vacom heat temperature over Modbus. */
int VacomSetHeat(itemCmd st);
/** (Legacy) Write dimmer value to the Modbus register of this item. */
int modbusDimmerSet(itemCmd st);
/** (Legacy) Low-level Modbus write of `value` to register `_reg` of slave `addr`. */
int modbusDimmerSet(int addr, uint16_t _reg, int _regType, int _mask, uint16_t value);
/** Mark the last Modbus transaction as failed (sets FLAG_SEND_RETRY). */
void mb_fail(int result=0);
/** Parse the item array: resolve type, arg/val/cmd/ext elements and the driver. */
void Parse();
/** (Legacy) Poll the Modbus dimmer register and update stored value/status. */
int checkModbusDimmer();
/** (Legacy) Compare a polled Modbus register `data` with the stored value and resync status. */
int checkModbusDimmer(int data);
/** Retry the last failed transaction if FLAG_SEND_RETRY is set. */
int checkRetry();
/** Publish a pending (deferred) status if the network is up. */
void sendDelayedStatus();
/** (Legacy) Poll Vacom fan motor registers and publish the state as JSON. */
int checkFM();
char defaultSubItem[16];
int defaultSuffixCode;
};
/**
* @brief Creates and shares output drivers for items.
*
* One driver instance is kept per channel type; getDriver() links the
* driver to the current item (abstractOut::link) and creates it on demand.
* Items of the same type share the same driver, saving RAM.
*/
class driverFactory {
public:
driverFactory(){memset(drivers,0,sizeof(drivers));};
/** @return item currently linked to the driver of this item, or NULL. */
Item * getItem(Item * item);
/** @return driver for the item's channel type (created on demand), linked to the item. */
abstractOut * getDriver(Item * item);
/** @return driver for the item's channel type without creating it (RGB types map to CH_DIMMER). */
abstractOut * findDriver(Item * item);
/** Unlink the item from its driver; delete the driver if no item uses it. */
void freeDriver(Item * item);
/** @return a new driver instance for the given channel type, or NULL if unsupported. */
abstractOut * newDriver(uint8_t itemType);
private:
/// Shared driver instance per channel type (CH_*)
abstractOut * drivers[CH_MAX+1];
};
/**
* @brief CH_THERMO runtime state packed into one int32 (stored in I_EXT).
*
* tempX100 - last measured temperature x 100 (low word),
* timestamp16 - millisNZ() timestamp of the measurement (high word).
*/
typedef union
{
struct
+156 -1
View File
@@ -21,6 +21,30 @@ e-mail anklimov@gmail.com
#include "Arduino.h"
#include "aJSON.h"
/**
* Textual names of item commands; array indexes match the CMD_* codes
* (CMD_ON=1 ... CMD_RESET=0x19).
*
* - "ON" (1) - turn channel ON
* - "OFF" (2) - turn channel OFF
* - "REST" (3) - restore: turn ON only if previously halted (CMD_HALT)
* - "TOGGLE" (4) - toggle ON/OFF
* - "HALT" (5) - turn OFF, stays locked until REST
* - "XON" (6) - momentary ON, stays on until XOFF
* - "XOFF" (7) - release a channel turned on by XON
* - "INCREASE" (8) / "DECREASE" (9) - step the value up / down
* - "ENABLE" (0xA) - enable channel (for PID - enable regulation; for XON/XOFF - lift limitation)
* - "DISABLE" (0xB) - disable channel
* - "UNFREEZE" (0xC) / "FREEZE" (0xD) - unlock / lock commands and values
* - "AUTO" (0xE) - thermostat/AC auto mode
* - "FAN_ONLY" (0xF) - AC fan-only mode
* - "HIGH" (0x10) / "MEDIUM" (0x11) / "LOW" (0x12) - AC/vent fan levels
* - "HEAT_COOL" (0x13) - AC auto heat/cool mode
* - "HEAT" (0x14) / "COOL" (0x15) / "DRY" (0x16) - thermostat/AC modes
* - "RGB" (0x17) - set color in RGB notation
* - "HSV" (0x18) - set color in HSV notation
* - "RESET" (0x19) - reset device to defaults
*/
typedef char cmdstr[10];
const cmdstr commands_P[] PROGMEM =
@@ -34,6 +58,10 @@ const cmdstr commands_P[] PROGMEM =
#define commandsNum sizeof(commands_P)/sizeof(cmdstr)
/**
* Short textual names of channel types (CH_*); array indexes match
* CH_DIMMER(0)..CH_SPRINKLER(23). Used to resolve "type":"DMX" etc. in config.
*/
typedef char ch_type[9];
const ch_type ch_type_P[] PROGMEM =
{
@@ -56,7 +84,7 @@ const ch_type ch_type_P[] PROGMEM =
"RELAYPWM", // 16 //Slow PWM relay to control objects with inertia
"DMXRGBWW", // 17 //DMX RGBWW channel (warm&cold white)
"VENTS", // 18 //Multichannel ventilation
"ELEVATOR", // 19 //
"ELEVATOR", // 19 //Elevator controller
"COUNTER", // 20 //Generic counter
"HUM", // 21 //Humidifier
"MERCURY", // 22 //Mercury energy meter/RS485 interface
@@ -66,6 +94,13 @@ const ch_type ch_type_P[] PROGMEM =
#define ch_typeNum sizeof(ch_type_P)/sizeof(ch_type)
/// Definition of Commands
/**
* Command codes (indexes of commands_P). Codes 1..9 are decimal,
* 0xA..0x19 are hex. Commands up to CMD_FAN (0xF) are "channel commands"
* that affect the whole channel state; the rest are device-specific
* (AC modes, color, reset). CMD_VOID/CMD_UNKNOWN/CMD_JSON are special
* non-wired codes used internally.
*/
#define CMD_ON 1 /// Turn channel ON
#define CMD_OFF 2 /// Turn channel OFF
#define CMD_RESTORE 3 /// Turn ON only if was previously turned off by CMD_HALT
@@ -97,7 +132,9 @@ const ch_type ch_type_P[] PROGMEM =
#define CMD_HSV 0x18
#define CMD_RESET 0x19
/// Low byte of the packed I_CMD int - command code
#define CMD_MASK 0xffUL
/// High two bytes of the packed I_CMD int - state flags (FLAG_*)
#define FLAG_MASK 0x00ffff00UL
//#define STATE_MASK 0xff0000
@@ -106,6 +143,23 @@ const ch_type ch_type_P[] PROGMEM =
#define CMD_JSON -2
//FLAGS
/**
* State flags packed with the command in the I_CMD item element (FLAG_MASK bits):
* - FLAG_DISABLED - channel disabled (needs ENABLE to unlock)
* - FLAG_LOCKED_CMD / FLAG_LOCKED_SET - frozen: commands / values are blocked
* - FLAG_FREEZED - both locked
* - FLAG_SEND_DEFFERED - status publish is pending (no network / low RAM)
* - FLAG_COMMAND / FLAG_PARAMETERS / FLAG_FLAGS - what to include in the next status
* - FLAG_SEND_RETRY - retry the failed Modbus transaction
* - FLAG_ACTION_NEEDED / FLAG_ACTION_IN_PROCESS - driver action state machine
* - FLAG_HALTED - turned off by CMD_HALT (CMD_RESTORE to return)
* - FLAG_XON - turned on by CMD_XON (CMD_XOFF to release)
* - FLAG_SEND_DELAYED - publish remaining time of a delayed command
*
* SendStatus options:
* - FLAG_SEND_IMMEDIATE - do not defer the publish
* - FLAG_NOT_SEND_CAN - skip publishing to CAN
*/
//#define FLAG_COMMAND 0x100UL
//#define FLAG_PARAMETERS 0x200UL
@@ -142,9 +196,18 @@ const ch_type ch_type_P[] PROGMEM =
#define FLAG_SEND_IMMEDIATE 0x1UL
#define FLAG_NOT_SEND_CAN 0x2UL
/**
* Translate a text payload to a command code.
* @param payload - text command word, number or color ("ON", "-5", "#FF0000")
* @return CMD_* code; CMD_VOID for plain numbers, CMD_UNKNOWN if unrecognized
*/
int txt2cmd (char * payload);
/**
* Storage type codes (ST_*) - how the 4-byte itemCmd parameter word
* is interpreted (value representation of a channel).
*/
///Definition of all possible types of argument, contained in class
#define ST_VOID 0 /// Not defined
#define ST_PERCENTS255 1 /// Percent value 0..255
@@ -165,11 +228,19 @@ int txt2cmd (char * payload);
#define ST_STRING 12 /// pointer to string (for further use)
//#define ST_TIMESTAMP 13 /// Timestamp for delayed cmd execution
/// Mapping options for doMapping()/doReverseMapping()
#define MAP_SCALE 1
#define MAP_VAL_CMD 2
#pragma pack(push, 1)
/**
* @brief Packed 32-bit command word of #itemCmd.
*
* cmdCode - command code (CMD_*), itemArgType - storage type of the
* parameter (ST_*), suffixCode - topic suffix (S_*), cmdEffect/cmdParam
* are reserved.
*/
typedef union
{
long int aslong;
@@ -185,6 +256,13 @@ typedef union
};
} itemCmdStore;
/**
* @brief 32-bit parameter storage of #itemCmd.
*
* Holds a signed/unsigned number, float or string pointer; a HSV255 color
* (h:9, s, v, colorTemp:7) and the same with signed fields; or a
* NeoPixel-packed BGWR color (b, g, r, w) compatible layout.
*/
typedef union
{
long int aslong;
@@ -215,85 +293,162 @@ typedef union
} itemArgStore;
class Item;
/**
* @brief Command + value container passed between items, drivers and MQTT.
*
* itemCmd packs a command code (CMD_*), a typed value (ST_* storage) and a
* topic suffix (S_*) into two 32-bit words (#itemCmdStore + #itemArgStore).
* It is the universal currency of the control core: parsed from text or
* JSON, converted to the channel's storage type, executed by the driver
* and serialized back to text for status publishing.
*/
class itemCmd
{
public:
/// Packed command word: code, storage type, suffix
itemCmdStore cmd;
/// Packed parameter: number / float / string / color
itemArgStore param;
/** Create a command of the given code with an empty parameter of the given storage type. */
itemCmd(uint8_t _type=ST_VOID, uint8_t _code=CMD_VOID);
/** Create a value with ST_FLOAT storage. */
itemCmd(float val);
/** Create a copy of the currently stored command/value of the item. */
itemCmd(Item *item);
/** Parse a text command/value ("ON", "50", "-1.5", "#FF0000", "HSV:100,50,255"). */
itemCmd(char *cmd);
/** Convert the value of `from` to the storage type appropriate for chanType and copy it to *this. */
itemCmd assignFrom(itemCmd from, short chanType=-1);
/** Load the stored command/value of the item into *this according to optionsFlag. */
bool loadItem(Item * item, uint16_t optionsFlag=FLAG_PARAMETERS);
/** Like loadItem(), but falls back to channel defaults for a never-controlled item. */
bool loadItemDef(Item * item, uint16_t optionsFlag=FLAG_PARAMETERS );
/** Write *this back to the item storage (command, value, flags) according to optionsFlag. */
bool saveItem(Item * item, uint16_t optionsFlag=FLAG_PARAMETERS);
/** Set the parameter to a signed 32-bit int (ST_INT32). */
itemCmd Int(int32_t i);
/** Set the parameter to an unsigned 32-bit int (ST_UINT32). */
itemCmd uInt(uint32_t i);
/** Set the parameter to a float (ST_FLOAT). */
itemCmd Float(float f);
/** Set the parameter to a fixed-point value in tens (ST_TENS, scaled by TENS_BASE). */
itemCmd Tens(int32_t i);
/** Set the parameter to a raw fixed-point value in tens (ST_TENS, pre-scaled). */
itemCmd Tens_raw(int32_t i);
/** Set the command code (and S_CMD suffix). */
itemCmd Cmd(uint8_t i);
/** Copy the command code and parameter from another itemCmd. */
itemCmd Cmd(itemCmd i);
/** Set an HSV255 color (hue 0..365, sat 0..100, value 0..255). */
itemCmd HSV(uint16_t h, uint8_t s, uint8_t v);
/** Set an HSV255 color (ST_HSV255 storage). */
itemCmd HSV255(uint16_t h, uint8_t s, uint8_t v);
/** Set hue and saturation only (ST_HS). */
itemCmd HS(uint16_t h, uint8_t s);
/** Set an RGB color (ST_RGB). */
itemCmd RGB(uint8_t r, uint8_t g, uint8_t b);
/** Set an RGBW color (ST_RGBW). */
itemCmd RGBW(uint8_t r, uint8_t g, uint8_t b, uint8_t w);
/** Set the parameter to a string pointer (ST_STRING). */
itemCmd Str(char * str);
/** Set the hue component (0..365). @return true on success. */
bool setH(uint16_t);
/** Set the saturation component (0..100). @return true on success. */
bool setS(uint8_t);
/** Set the color temperature in mireds (153..500). @return true on success. */
bool setColorTemp(int);
/** @return stored color temperature in mireds (153..500), -1 if none. */
int getColorTemp();
/** @return stored hue (0..365). */
uint16_t getH();
/** @return stored saturation (0..100). */
uint16_t getS();
/** Change the parameter storage type (ST_*). */
itemCmd setArgType(uint8_t);
/** Convert the stored value to the given storage type (ST_*). */
itemCmd convertTo(uint8_t);
/** @return the default storage type (ST_*) for the given channel type. */
uint8_t getStoragetypeByChanType(short chanType);
/** Set the parameter to a percent value 0..100 (ST_PERCENTS255 storage). */
itemCmd Percents(int i);
/** Set the parameter to a percent value 0..255 (ST_PERCENTS255 storage). */
itemCmd Percents255(int i);
/** @return the topic suffix code (S_*) of this command. */
uint8_t getSuffix();
/** Set the topic suffix code (S_*). */
itemCmd setSuffix(uint8_t suffix);
/** Increase/decrease a percent/int/float value by `step`, clamped to [0, limit]. */
bool incrementPercents(long int, long int limit);
/** Increase/decrease the hue by `step` (with wrap-around). */
bool incrementH(long int);
/** Increase/decrease the saturation by `step`. */
bool incrementS(long int);
/** Increase/decrease the temperature by `step`. */
bool incrementTemp(long int dif);
/** @return the parameter as a signed 32-bit int. */
long int getInt();
/** @return the parameter as a fixed-point value in tens. */
long int getTens();
/** @return the raw fixed-point parameter (no scaling). */
long int getTens_raw();
/** @return the parameter as a float. */
float getFloat();
/** @return the parameter as a string (ST_STRING). */
char * getString();
/** @return the parameter as a single int (first byte of a color, etc). */
long int getSingleInt();
/** @return the value as percent 0..100 (inverse flips low/high). */
short getPercents(bool inverse=false);
/** @return the value as percent 0..255. */
short getPercents255(bool inverse=false);
/** Set the value from a percent 0..100. @return true on success. */
bool setPercents(int percents);
/** @return the command code (CMD_*). */
uint8_t getCmd();
/** @return the parameter storage type (ST_*). */
uint8_t getArgType();
/** @return the reserved cmdParam field. */
uint8_t getCmdParam();
/**
* Serialize the command and/or value to a text payload for MQTT.
* @param sendFlags - FLAG_COMMAND and/or FLAG_PARAMETERS select what to print
* @param scale100 - scale 0..255 values to 0..100
*/
char * toString(char * Buffer, int bufLen, int sendFlags = FLAG_COMMAND | FLAG_PARAMETERS, bool scale100 = false);
/** @return true if this is a command (not just a value). */
bool isCommand();
/** @return true if this is a whole-channel command (not a subcommand). */
bool isChannelCommand();
/** @return true if this carries a value. */
bool isValue();
/** @return true if this carries a color (HSV/RGB/RGBW). */
bool isColor();
/** Set the value to the channel default for the current storage type. */
itemCmd setDefault();
/** Set the storage type appropriate for the given channel type. */
itemCmd setChanType(short chanType);
/** Dump the command to the debug serial. */
void debugOut();
/** Map the unified command/value to a device-specific value via `mappingData`. */
itemCmd doMapping(aJsonObject *mappingData);
/** Map a device-specific value back to the unified representation. */
itemCmd doReverseMapping (aJsonObject *mappingData);
/** Scale a 0..255 value to 0..100 in place. @return true on success. */
bool scale100();
};
/** @return the channel type code (CH_*) for a config "type" verb (string or int). */
int replaceTypeToInt(aJsonObject* verb);
#pragma pack(pop)
+215
View File
@@ -1,3 +1,16 @@
/**
* @file main.h
* @brief Main controller interface of LightHub.
*
* Declares the application entry points (setup_main/loop_main), the
* LAN/MQTT state machine (lan_status, lanLoop) and its initialization
* routines (onInitialStateInitLAN, onMQTTConnect,
* ip_ready_config_loaded_connecting_to_broker), remote config handling
* (loadConfigFromHttp, loadConfigFromEEPROM, applyConfig), the serial CLI
* command handlers (cmdFunction*) and the periodic polling loops
* (pollingLoop, thermoLoop, inputLoop) that drive items, thermostats
* and inputs.
*/
#pragma once
#include "options.h"
@@ -214,6 +227,13 @@ extern Streamlog errorSerial;
extern Artnet *artnet;
#endif
/**
* @brief LAN/MQTT boot-up and runtime state machine states.
*
* Positive values are boot/operation states; negative and high values
* are transient actions requested by loop_main (reinit, reconnect,
* config re-read, get).
*/
enum lan_status {
INITIAL_STATE = 0,
AWAITING_ADDRESS = 1,
@@ -236,52 +256,159 @@ enum lan_status {
AWAITING_CONFIG = 19
};
/** @brief Current state of the LAN/MQTT state machine. */
extern lan_status lanStatus;
/** @brief Chip unique ID (20 bytes), viewable as 5 longs or 20 bytes. */
typedef union {
uint32_t UID_Long[5];
uint8_t UID_Byte[20];
} UID;
/**
* @brief Checks whether the controller is not in the retain/collecting phase.
* @return true if lanStatus is not RETAINING_COLLECTING.
*/
bool isNotRetainingStatus();
#if not defined (NOIP)
/**
* @brief MQTT receive callback: dispatches incoming messages to item
* command processing or config handling.
* @param topic MQTT topic of the received message.
* @param payload Message payload (not necessarily null-terminated).
* @param length Payload length in bytes.
*/
void mqttCallback(char *topic, byte *payload, unsigned int length);
/**
* @brief One iteration of the LAN/MQTT state machine; advances the
* ethernet/WiFi/MQTT boot sequence and returns the new state.
* @return Next lan_status for the main loop.
*/
lan_status lanLoop();
/**
* @brief Downloads the device JSON config from the config server
* (HTTP GET with If-None-Match ETag), parses and applies it.
* @return HTTP status code (200 on success, 304 if unchanged, negative on error).
*/
int loadConfigFromHttp();
/**
* @brief Initial LAN setup: initializes ethernet/WiFi and starts the
* address acquisition sequence.
*/
void onInitialStateInitLAN();
/**
* @brief Called on (re)connect to the MQTT broker: sets up topics,
* subscribes and publishes retained states.
*/
void onMQTTConnect();
/**
* @brief State handler for IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER:
* connects to the configured MQTT broker(s).
*/
void ip_ready_config_loaded_connecting_to_broker();
#endif
/**
* @brief Resolves the controller MAC address (from flash or chip UID)
* and stores it in the system config.
*/
void setupMacAddress();
/**
* @brief Prints the current MAC address to the info log.
*/
void printMACAddress();
#ifndef OWIRE_DISABLE
/**
* @brief 1-Wire temperature change callback.
* @param i Sensor index.
* @param addr DeviceAddress of the sensor.
* @param currentTemp Newly read temperature in Celsius.
*/
void Changed(int i, DeviceAddress addr, float currentTemp);
#endif
/**
* @brief Idle hook invoked by the Modbus code between serial operations
* to let the main system breathe.
*/
void modbusIdle(void);
/**
* @brief CLI command "help": lists available CLI commands.
* @param arg_cnt Number of arguments.
* @param args Argument strings.
* @return Command result code.
*/
int cmdFunctionHelp(int arg_cnt, char **args);
/**
* @brief CLI command "kill": aborts the current long operation.
* @param arg_cnt Number of arguments.
* @param args Argument strings.
* @return Command result code.
*/
int cmdFunctionKill(int arg_cnt, char **args);
/**
* @brief Applies the loaded JSON config tree to the running system
* (items, inputs, topics, drivers).
* @return true if the config was applied successfully.
*/
bool applyConfig();
/**
* @brief CLI command "load": loads the config from the portal or flash.
* @param arg_cnt Number of arguments.
* @param args Argument strings.
* @return Command result code.
*/
int cmdFunctionLoad(int arg_cnt, char **args);
/**
* @brief Loads the last saved config from flash into the config tree.
* @return 0 on success, nonzero on error.
*/
int loadConfigFromEEPROM();
/**
* @brief CLI command "save": persists the current config tree to flash.
* @param arg_cnt Number of arguments.
* @param args Argument strings.
* @return Command result code.
*/
int cmdFunctionSave(int arg_cnt, char **args);
/**
* @brief CLI command "mac": changes the controller MAC address.
* @param arg_cnt Number of arguments.
* @param args Argument strings (new MAC).
* @return Command result code.
*/
int cmdFunctionSetMac(int arg_cnt, char **args);
/**
* @brief CLI command "get": re-downloads the config from the portal.
* @param arg_cnt Number of arguments.
* @param args Argument strings.
* @return Command result code.
*/
int cmdFunctionGet(int arg_cnt, char **args);
/**
* @brief CLI command "log": changes serial/UDP log levels.
* @param arg_cnt Number of arguments.
* @param args Argument strings (levels).
* @return Command result code.
*/
int cmdFunctionLoglevel(int arg_cnt, char **args);
/**
* @brief Prints a bool as "+" or "-" to the info log.
* @param arg Value to print.
*/
void printBool(bool arg);
int cmdFunctionSave(int arg_cnt, char **args);
@@ -297,52 +424,140 @@ int ipLoadFromFlash(short n, IPAddress &ip);
/**
* @brief Marks that an Ethernet transmission is about to happen;
* disables time-sensitive drivers as needed.
*/
void preTransmission();
/**
* @brief Marks that an Ethernet transmission has finished;
* re-enables drivers and handles lost-time compensation.
*/
void postTransmission();
/**
* @brief Application setup: init serial, flash config, MAC, LAN,
* HTTP/CLI listeners and all drivers (runs once at boot).
*/
void setup_main();
/**
* @brief Application main loop: runs the LAN/MQTT state machine, CLI
* poller, item polling, thermostat, input, DMX and CAN iterations.
*/
void loop_main();
/**
* @brief Idle hook invoked by the 1-Wire/DS2482 code between operations.
*/
void owIdle(void);
void modbusIdle(void);
/**
* @brief Polls digital/analog inputs and processes their events.
* @param cause Why the loop is running (e.g. CHECK_INPUT).
*/
void inputLoop(short);
/**
* @brief Polls periodic sensors (DHT, ultrasonic, ...) connected to inputs.
*/
void inputSensorsLoop();
/**
* @brief Sets up input channels from the loaded config.
*/
void inputSetup(void);
/**
* @brief Stops and releases all input channels.
*/
void inputStop(void);
/**
* @brief Periodic item polling: refreshes states and publishes changes.
*/
void pollingLoop(void);
/**
* @brief Thermostat control loop: checks temperature readings and
* drives heater relays (on/off/error bypass) with overheat alarms.
*/
void thermoLoop(void);
/**
* @brief Updates the current-temperature store of a thermostat by name.
* @param name Name of the thermostat item.
* @param t New temperature in Celsius.
* @return 0 on success, nonzero if the item was not found.
*/
short thermoSetCurTemp(char *name, float t);
void modbusIdle(void);
/**
* @brief Prints a summary of the current network/system config.
*/
void printConfigSummary();
/**
* @brief Registers the serial CLI commands (help, save, load, get,
* mac, ip, kill, reboot, log, ...) with the Cmd library.
*/
void setupCmdArduino();
/**
* @brief Prints firmware version, revision and build options at boot.
*/
void printFirmwareVersionAndBuildOptions();
/**
* @brief Checks whether a config array entry is a thermostat item.
* @param item aJsonObject config array to check.
* @return true if the entry is a CH_THERMO item.
*/
bool IsThermostat(const aJsonObject *item);
/**
* @brief Checks whether a thermostat must be stopped (disabled or its
* temperature sensor disconnected for too long).
* @param thermoExtensionArray Thermostat extension array from config.
* @param thermoLatestCommand Last received thermostat command.
* @return true if the thermostat should be disabled.
*/
bool disabledDisconnected(const aJsonObject *thermoExtensionArray, int thermoLatestCommand);
/**
* @brief Hard-resets the board (watchdog or MCU reset).
*/
void resetHard();
/**
* @brief Frees the current config tree and its driver resources.
* @param locksAlowed Number of config locks that may be active.
* @return true if the config was successfully released.
*/
bool cleanConf(short locksAlowed=0);
/**
* @brief Prints the current LAN configuration to the serial port.
*/
void printCurentLanConfig();
/**
* @brief Attaches the Matura timer (interrupt-based timing helper).
* @return Attached timer number, or negative on error.
*/
int16_t attachMaturaTimer();
/**
* @brief Selects the first configured MQTT broker for connection.
*/
void setFirstBroker();
/**
* @brief Moves to the next configured MQTT broker (failover).
*/
void setNextBroker();
//void printFreeRam();
+28
View File
@@ -12,6 +12,7 @@
//#endif
//#define CCS811_ADDR 0x5B //Default I2C Address
/// Fixed I2C address of the CCS811 sensor (alternate address).
#define CCS811_ADDR 0x5A //Alternate I2C Address
#if defined (ARDUINO_ARCH_ESP8266)
@@ -50,6 +51,20 @@
class Input;
/**
* @brief CCS811 eCO2 / eTVOC air-quality sensor input (IN_CCS811 = 5).
*
* Reads the MOX air-quality sensor over I2C at the fixed address
* CCS811_ADDR (0x5A). Polls the sensor when new data is available,
* suppresses spontaneous calculation errors (CO2 >= 10000 ppm) and
* publishes the readings on the input "in" section:
* - /CO2 - estimated CO2 concentration (ppm);
* - /TVOC - estimated total volatile organic compounds (ppb);
* - /base - sensor baseline value.
*
* The humidity/temperature measured by the companion HDC1080 input are
* forwarded here via setEnvironmentalData() to improve accuracy.
*/
class in_ccs811 : public abstractIn {
public:
//CCS811 ccs811(CCS811_ADDR);
@@ -63,6 +78,19 @@ protected:
void printSensorError();
};
/**
* @brief HDC1080 temperature / humidity sensor input (IN_HDC1080 = 6).
*
* Reads the humidity/temperature sensor over I2C at the fixed address
* 0x40. Polls on the slow polling cycle, resets the I2C bus on a read
* glitch, publishes the readings on the input "in" section and routes
* the humidity value to the "hum" key of the input object:
* - /T - temperature (Celsius);
* - /H - relative humidity (percent).
*
* The measured humidity/temperature are also forwarded to the CCS811
* sensor (when present) via setEnvironmentalData().
*/
class in_hdc1080 : public abstractIn {
public:
//ClosedCube_HDC1080 hdc1080;
+21
View File
@@ -6,12 +6,33 @@
#include <abstractout.h>
#include <item.h>
/**
* @brief Auto-summing (virtual) counter driver, channel type CH_COUNTER = 20.
*
* No GPIO involved: every configured period the driver adds a fixed impulse
* value to the stored channel value (software "tick" generator).
*
* JSON config (itemArg): [impulse_coef, period_sec]
* - impulse_coef: value added to the counter on each tick (may be fractional);
* - period_sec: period between ticks in seconds.
*
* Ctrl:
* - /set - a non-zero value starts accumulation, zero stops it;
* - /cmd - on-like commands (on/heat/cool/auto/fan/dry/heatcool) start, off stops.
*
* Status: the accumulated counter value, republished after every tick.
*/
class out_counter : public abstractOut {
public:
/** @brief Initializes the channel state (CST_INITIALIZED); no hardware. */
int Setup() override;
/** @brief When the tick period expires, adds impulse_coef to the stored value and sends the status. */
int Poll(short cause) override;
/** @brief Marks the channel as unknown. */
int Stop() override;
/** @brief Returns CH_COUNTER (20). */
int getChanType() override;
/** @brief Starts/stops accumulation via /set (non-zero value) and /cmd on/off (see class doc). */
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
};
#endif
+14 -1
View File
@@ -7,12 +7,25 @@
#include <item.h>
#include "colorchannel.h"
/**
* @brief DMX512 output driver.
*
* Channel type depends on the number of configured DMX channels:
* 1-2 -> CH_DIMMER = 0, 3 -> CH_RGB = 2, 4 -> CH_RGBW = 1, 5 -> CH_RGBWW = 17.
*
* JSON config (itemArg): [dch1, dch2, ...] - 1 to 5 DMX channel numbers
* starting at the channel base address.
*/
class out_dmx : public colorChannel {
public:
/** @brief Initializes the channel state (CST_INITIALIZED). */
int Setup() override;
/** @brief Marks the channel as unknown. */
int Stop() override;
/** @brief Returns CH_RGB / CH_RGBW / CH_RGBWW based on the number of configured channels (otherwise the item type). */
int getChanType() override;
/** @brief Writes the command value to the DMX channels: single-channel dimmer percents, or RGB/RGBW/RGBWW color (color temperature split into the two white channels for RGBWW). */
virtual int PixelCtrl(itemCmd cmd, char* subItem=NULL, bool show=true, bool authorized = false) override;
protected:
+40 -1
View File
@@ -9,6 +9,9 @@
/**
* @brief Persistent per-item state of the meter channel, stored in the config tree.
*/
class mercuryPersistent : public chPersistent {
public:
@@ -16,10 +19,17 @@ public:
uint32_t lastSuccessTS;
};
/** execObj subtype markers (shared with the Modbus driver). */
#define MB_NEED_SEND 8
#define MB_SEND_ERROR 4
#define MB_SEND_ATTEMPTS 3
/**
* @brief Session states of the meter polling state machine:
* M_CONNECTING (login), M_CONNECTED (idle between polls) and
* M_POLLING1..M_POLLING8 (voltage, current, angles, counters, power, cosf
* steps of one polling cycle).
*/
#define M_CONNECTING CST_USER+0
#define M_CONNECTED CST_USER+1
#define M_POLLING1 CST_USER+2
@@ -31,6 +41,7 @@ public:
#define M_POLLING7 CST_USER+8
#define M_POLLING8 CST_USER+9
/** Transaction result codes returned by the meter commands. */
#define RET_SUCCESS 0
#define RET_INVALID_PARAM 1
#define RET_INTERROR 2
@@ -39,17 +50,45 @@ public:
#define RET_NOT_CONNECTED 3
/**
* @brief "Mercury" 3-phase electricity meter driver (Owen Modbus protocol),
* channel type CH_MERCURY = 22.
*
* Connects to the meter over RS485, opens a session with an access level and
* a 6-character password, and polls the measurements every
* pollingInterval. The session is dropped and re-established after
* 240 s without a successful transaction.
*
* JSON config (itemArg):
* [addr, baud(default 9600), "8N1", accessLevel, password,
* pollingInterval_ms(default 10000)]
* - password: a 6-character string or an array of 6 digits.
*
* Session states: M_CONNECTING, M_CONNECTED, M_POLLING1..M_POLLING8 (see above).
*
* Published status topics: /voltage/A|B|C, /current/A|B|C, /angles/A|B|C,
* /counters/Active, /counters/Reactive, /power/A|B|C, /cosf/sum|A|B|C.
*
* Ctrl: not implemented (read-only channel).
*/
class out_Mercury : public abstractOut {
public:
//out_Mercury(Item * _item):abstractOut(_item){store = (mercuryPersistent *) item->getPersistent();};
/** @brief Default constructor; binds nothing until link() is called. */
out_Mercury():store(NULL){};
/** @brief Links the channel item and attaches persistent storage. */
void link(Item * _item){abstractOut::link(_item); if (_item) {store = (mercuryPersistent *) item->getPersistent();} else store = NULL;};
/** @brief Creates persistent storage, loads the config and initializes the RS485 line. */
int Setup() override;
/** @brief Runs the connect/poll state machine; on each successful step publishes the measured values (see class doc). */
int Poll(short cause) override;
/** @brief Closes the meter session, releases the persistent storage. */
int Stop() override;
/** @brief Returns CH_MBUS (14). */
int getChanType() override;
/** @brief Not implemented, always returns -1 (read-only channel). */
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
+36
View File
@@ -8,6 +8,13 @@
/**
* @brief Persistent per-item state of a Modbus channel, stored in the config tree.
*
* Holds the line parameters, the polling schedule/interval and the template
* references: polled register lists (holding regs, input regs, coils,
* discrete inputs) and the template parameter table.
*/
class mbPersistent : public chPersistent {
public:
@@ -33,16 +40,45 @@ public:
#define LM_VALUE_EMPTY 2
/**
* @brief Generic Modbus register channel driver, channel type CH_MBUS = 14.
*
* The channel is described by a template from the global Modbus template
* table: registers are polled periodically, mapped to item parameters and
* fired as execution actions, and control commands are written back to the
* registers. Last sent / last measured values are tracked per parameter in
* the "@V" / "@S" marks to suppress redundant writes and re-executions.
*
* JSON config (itemArg): [addr, "template", {params}]
* - addr: Modbus unit address;
* - "template": name of a template in the global modbus settings
* (keys: serial, baud, poll{regs, irs, coils, dins, delay}, par);
* - {params}: per-parameter objects; a template parameter supports
* reg/ir/coil/din (address), type, id (suffix code), map, def, prefetch.
*
* Register value types: i16, i32, u16, u32, i8h, i8l, u8h, u8l, x10, 100.
*
* Ctrl: the subitem (template parameter name) takes priority, otherwise the
* suffix code is matched against the "id" of the template parameters
* (e.g. S_CMD / S_SET or a custom numeric id).
*/
class out_Modbus : public abstractOut {
public:
//out_Modbus(Item * _item):abstractOut(_item){store = (mbPersistent *) item->getPersistent();};
/** @brief Default constructor; binds nothing until link() is called. */
out_Modbus():store(NULL){};
/** @brief Links the channel item and attaches persistent storage. */
void link(Item * _item){abstractOut::link(_item); if (_item) {store = (mbPersistent *) item->getPersistent(); } else store = NULL;};
/** @brief Creates persistent storage, loads the template config and prepares the @S marks. */
int Setup() override;
/** @brief Sends queued (MB_NEED_SEND) parameter values, then polls the configured register lists and executes mapped actions. */
int Poll(short cause) override;
/** @brief Releases the persistent storage. */
int Stop() override;
/** @brief Returns CH_MBUS (14). */
int getChanType() override;
/** @brief Queues a command for a template parameter (by subitem name or by suffix id), see class doc. */
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
//int getDefaultStorageType(){return ST_INT32;};
//int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override;
+24
View File
@@ -17,19 +17,43 @@
static int8_t motorQuote = MOTOR_QUOTE;
/**
* @brief Motorized gate/shutter driver with analog position feedback,
* channel type CH_MOTOR = 12.
*
* Drives the up/down motor pins (with PWM speed control where the pins
* support PWM) until the analog feedback reaches the target position
* 0-255. No more than MOTOR_QUOTE (2) motors run simultaneously, and the
* motor is stopped after maxOnTime ms regardless of position.
*
* JSON config (itemArg):
* [pinUp, pinDown, pinFeedback, fbOpen, fbClosed, maxOnTime_ms(default 10000)]
* - negative pin numbers invert the output; defaults pinUp=32, pinDown=33;
* - fbOpen / fbClosed: raw analog readings at fully open / fully closed.
*
* Ctrl: /set <0-255> - target position; /cmd on / off (off drives to 0).
*
* The current position (0-255) is derived from the analog feedback pin.
*/
class out_Motor : public abstractOut {
public:
//out_Motor(Item * _item):abstractOut(_item){getConfig();};
//out_Motor(){};
/** @brief Links the channel item and reads the pin config from itemArg. */
void link(Item * _item){abstractOut::link(_item); if (_item) getConfig();};
/** @brief Sets the up/down pins as outputs, the feedback pin as input, resets the action flags. */
int Setup() override;
/** @brief Drives the motor toward the target position with speed ramp, enforces the max on-time, and stops at target. */
int Poll(short cause) override;
/** @brief Deactivates both motor outputs and resets the channel state. */
int Stop() override;
//int isActive() override;
/** @brief Returns CH_PWM (3). */
int getChanType() override;
//int getDefaultStorageType(){return ST_PERCENTS255;};
//int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override;
/** @brief Sets the target position via /set (0-255) and /cmd on/off, requesting a move (see class doc). */
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
int8_t pinUp;
+45
View File
@@ -6,6 +6,15 @@
#include "itemCmd.h"
#include <PID_v1.h>
/**
* @brief Boost parameters of the shared AC node.
*
* When the balance of the zone PID outputs exceeds AC_BOOST_TRESHOLD (230),
* the AC setpoint is boosted to AC_BOOST_HIGH_TEMP (30) for heating or
* AC_BOOST_LOW_TEMP (17) for cooling, and restored to AC_PRESET_TEMP (21)
* when the balance falls back within AC_BOOST_DEADBAND (10).
* AC_HEATCOOL_DEADBAND is the temperature deadband of HEATCOOL zones.
*/
#define AC_BOOST_HIGH_TEMP 30
#define AC_PRESET_TEMP 21
#define AC_BOOST_LOW_TEMP 17
@@ -13,25 +22,61 @@
#define AC_BOOST_DEADBAND 10
#define AC_HEATCOOL_DEADBAND 1.0
/** @brief Bitmask flags (zone "supress") suppressing redundant commands to the AC. */
#define AC_SUPPRESS_FAN 1
#define AC_SUPPRESS_SET 2
#define AC_SUPPRESS_CMD 4
#define AC_SUPPRESS_ALL 7
/**
* @brief Multi-zone ventilation controller for a shared AC unit,
* channel type CH_MULTIVENT = 18.
*
* Manages several ventilation zones (per-zone setpoints, PID control and
* airflow capacity) and steers the shared AC unit (mode, fan level and
* setpoint), including boost on PID imbalance and suppression of redundant
* commands to the AC.
*
* JSON config (itemArg = OBJECT of zones). The key "" is the shared AC node.
* Zone keys: "set" (setpoint), "fan", "cmd", "V" (airflow capacity,
* default 60), "pid" ([kP, kI, kD, dT(default 5 s)]), "AlarmTime"
* (default 120 s), "AlarmValue". AC node keys: "boost" (default 230),
* "supress" (bitmask: 1 fan, 2 set, 4 cmd, 7 all; default 0xF),
* "turnbyfan", "overridecmd", "onextcmd", "cas" (cascade execution object).
*
* Ctrl commands:
* - /fan - LOW / MEDIUM / HIGH (64/128/255), a 0-255 value, AUTO (passive
* mode) or OFF;
* - /cmd - ON, OFF, HEAT, COOL, HEATCOOL, DRY, FAN, AUTO, FREEZE, UNFREEZE;
* - /set - zone temperature setpoint;
* - /val - zone temperature (PID input).
* - Without a subitem the call is AC feedback (/val, /fan, /set, /mode, /cmd).
*
* Poll: runs the per-zone PID loops, redistributes airflow by V capacity,
* applies boost/suppress and sends the resulting fan level and mode to the AC.
*/
class out_Multivent : public abstractOut {
public:
//out_Multivent(Item * _item):abstractOut(_item){getConfig();};
//out_Multivent(){};
/** @brief Links the channel item and reads the zones object from itemArg. */
void link(Item * _item){abstractOut::link(_item); if (_item) getConfig();};
/** @brief Creates the per-zone state (fan/cmd/out) and allocates the PID controllers. */
int Setup() override;
/** @brief Per-zone PID control, airflow redistribution, boost/suppress and AC fan/mode control (see class doc). */
int Poll(short cause) override;
/** @brief Deletes the PID controllers and marks the channel as unknown. */
int Stop() override;
/** @brief Returns 1 if any zone requests an active command, 0 otherwise. */
int isActive() override;
/** @brief Returns CH_THERMO (5). */
int getChanType() override;
//int getDefaultStorageType(){return ST_PERCENTS255;};
/** @brief Applies a command to a zone (or the AC without subitem) and forwards the resulting fan level to the AC, see class doc. */
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
/** @brief Zone fan/mode/set/val handling with passive mode, freeze and PID interlocks; returns the resulting AC fan level (0-255), -1 (no change, OK) or -2 (rejected). */
int fanCtrl(itemCmd cmd, char* subItem=NULL, bool force = false);
protected:
void getConfig();
+63 -1
View File
@@ -7,19 +7,40 @@
#include "itemCmd.h"
#include "aJson.h"
/** Minimum absolute change of the PID output (in output units) required
* to trigger an update of the controlled output. */
#define OUTPUT_TRESHOLD 1.0
/**
* @brief Lightweight holder for PID-related JSON objects.
*
* Stores a pointer to aJSON objects that carry the PID item values
* (input, setpoint, output). Currently the accessors return NULL and
* only the embedded PID controller pointer is retrieved from the object.
*/
class pidJson {
public:
/// Constructor, stores the JSON object pointer.
pidJson(aJsonObject * _obj){obj=_obj;};
/// Get the JSON object holding the PID input value (not used yet).
aJsonObject * getValObj(){return NULL;};
/// Get the JSON object holding the PID setpoint (not used yet).
aJsonObject * getSetObj(){return NULL;};
/// Get the JSON object holding the PID output (not used yet).
aJsonObject * getPoObj(){return NULL;};
/// Get the PID controller pointer stored inside the JSON object value.
PID * getPID(){return (obj) ? (PID*) obj->valueint: NULL;};
aJsonObject * obj;
};
/**
* @brief Persistent (survives reboot) state of the PID driver.
*
* Stored in the channel persistent area: the PID controller instance,
* the current input / output / setpoint process values, the last output
* actually applied and the input-failure (alarm) timer state.
*/
class pidPersistent : public chPersistent {
public:
PID * pid;
@@ -33,27 +54,68 @@ public:
};
/**
* @brief PID controller output driver (CH_PID = 13).
*
* Runs a classic PID loop (PID_v1) and steers an external output item
* (e.g. a dimmer) with the computed control value.
*
* JSON config (item argument array):
* @code
* [13,
* [kP, kI, kD, dT, alarmTO, alarmVal, outMin, outMax], // PID params, 1..8 elems, or 6 elems
* outputExecObj, // command object applied to the controlled output
* cascadeExecObj] // optional: object receiving the setpoint (cascade)
* @endcode
* - PID params array, trailing elements are optional: kP, kI, kD,
* dT (sample time, sec), alarmTO (input-failure timeout, sec,
* default 600 s), alarmVal (forced output value applied on alarm),
* outMin, outMax (output limits, default 0..255). A negative kP
* selects REVERSE direction.
* - The initial setpoint is taken from the item "val" entry
* (default 20.0).
*
* Control (Ctrl) suffixes:
* - /val - measured input value fed to the controller (also
* disarms the input-failure alarm);
* - /set - setpoint, additionally forwarded to cascadeExecObj;
* - /cmd - AUTO / HEAT / COOL / ON / OFF / ENABLE / DISABLE / RESET;
* - /ctrl - ENABLE / DISABLE (freeze the control action).
*
* If no /val is received for alarmTO seconds the driver enters the
* alarm state and forces the output to alarmVal.
*/
class out_pid : public abstractOut {
public:
//out_pid(Item * _item):abstractOut(_item){store = (pidPersistent *) item->getPersistent();};
/// Default constructor.
out_pid():store(NULL){};
/// Attach to an item and grab its persistent PID state.
void link(Item * _item){abstractOut::link(_item); if (_item) {store = (pidPersistent *) item->getPersistent();} else store = NULL;};
/// Allocate persistent state, load JSON config, initialize the PID controller.
int Setup() override;
/// Run the PID loop: compute output, apply it to the output item, watch the input-failure timer.
int Poll(short cause) override;
/// De-initialize and free the PID controller and persistent state.
int Stop() override;
/// True when the item command is not CMD_OFF.
int isActive() override;
/// Channel type of the driver.
int getChanType() override;
//int getDefaultStorageType(){return ST_FLOAT;};
/// Handle /val (input), /set (setpoint) and /cmd|/ctrl (ON/OFF/ENABLE/DISABLE/RESET) commands.
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
/// Enter (state=true) or leave (state=false) the input-failure alarm; forces the alarmVal output on entry.
void alarm(bool);
protected:
pidPersistent * store;
/// Parse the item argument array, fill store and create the PID controller.
bool getConfig();
/// Get the forced output value used on alarm (alarmVal, or 0/255 by control direction).
float getAlarmVal();
};
#endif
+26
View File
@@ -7,16 +7,42 @@
#include <item.h>
#include "colorchannel.h"
/**
* @brief Hardware PWM output driver (config channel type CH_PWM = 3).
*
* Drives one or more pins with analogWrite() PWM. The number of
* configured pins defines the color channel type:
* - 1 pin - single PWM (percent) output;
* - 3 pins - CH_RGB (3), R/G/B;
* - 4 pins - CH_RGBW (1), R/G/B + white;
* - 5 pins - CH_RGBWW (17), R/G/B + warm/white.
*
* JSON config (item argument array):
* @code
* [3, [pin0, pin1, pin2, (pin3, (pin4))]]
* @endcode
*
* On Mega2560 the hardware PWM of timers 3 and 4 is reconfigured to a
* 4000 Hz frequency (prescaler = 2) so the white channels stay in sync
* with the RGB ones. On ESP32 the driver is a stub (no PWM yet).
*
* Accepts the standard colorChannel control suffixes: /set (percent or
* color value), /HSV, /cmd ON/OFF.
*/
class out_pwm : public colorChannel {
public:
// out_pwm():numChannels(0){};
/// Configure pin directions and (on Mega2560) the 4000 Hz PWM timers.
int Setup() override;
/// Release the pins back to INPUT and mark the driver uninitialized.
int Stop() override;
/// Channel type derived from the number of configured pins (RGB/RGBW/RGBWW) or the raw item type.
int getChanType() override;
//int Ctrl(itemCmd cmd, char* subItem=NULL) override;
/// Apply the command to the PWM pins (single percent or R/G/B/(W) values).
int PixelCtrl(itemCmd cmd, char* subItem=NULL, bool show=true, bool authorized = false ) override;
//protected:
+26
View File
@@ -6,16 +6,42 @@
#include <abstractout.h>
#include <item.h>
/**
* @brief Relay (ON/OFF output) driver (CH_RELAY = 6 / CH_RELAYX = 16).
*
* Switches a digital output pin. In the simple CH_RELAY form the relay
* is latched; in the CH_RELAYX form it is a pulse output: after each
* turn-on the relay stays active for `val`/255 of the configured period
* and is switched off by the polling loop.
*
* JSON config (item argument array):
* - CH_RELAY: `[6, pin, val, cmd]`
* - CH_RELAYX: `[16, [pin, period_sec], val, cmd]`, period in seconds
* (default 5).
*
* A negative pin number selects an inverted output (active-low).
*
* Commands (/cmd): ON / OFF / TOGGLE / XON / XOFF / HALT / REST,
* plus a /set percent value which defines the ON duration for CH_RELAYX.
*/
class out_relay : public abstractOut {
public:
/// Attach to an item and read its pin/period configuration.
void link(Item * _item){abstractOut::link(_item); if (_item) getConfig();};
/// Read pin and pulse period from the item arguments (negative pin = inverted).
void getConfig();
/// Physically switch the relay and publish the new action state.
void relay(bool state);
/// Initialize the pin (OUTPUT, inactive) and remember the state if the item is active.
int Setup() override;
/// For CH_RELAYX: toggle the relay on/off according to the configured period and /set value.
int Poll(short cause) override;
/// Release the pin and mark the driver uninitialized.
int Stop() override;
/// Channel type of the driver.
int getChanType() override;
/// Handle /set (duration/turn-on) and /cmd (ON/OFF/TOGGLE/XON/XOFF/HALT/REST) commands.
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
protected:
+29
View File
@@ -12,23 +12,52 @@
#include "FastLED.h"
#endif
/**
* @brief Addressable LED strip driver, SPI-like data protocol (CH_SPILED = 11).
*
* Drives a NeoPixel-compatible LED strip (FastLED or Adafruit_NeoPixel
* backend) from a single data pin.
*
* JSON config (item argument array):
* @code
* [11, [data_pin, numLeds, ledsType]]
* @endcode
* - data_pin - the strip data line (default 3);
* - numLeds - number of LEDs on the strip (default 43);
* - ledsType - LED data order / speed (e.g. NeoPixel flags),
* default BRG 800 kHz.
*
* The channel type is derived from ledsType: RGBW if the strip has a
* white channel, RGB otherwise. A subItem "from-to" (or a single LED
* number) limits the command to an LED range, otherwise the whole
* strip is affected.
*/
class out_SPILed : public colorChannel {
public:
//out_SPILed(Item * _item):colorChannel(_item){getConfig();};
//out_SPILed(){};
/// Attach to an item and read the strip configuration.
void link(Item * _item){colorChannel::link(_item);if (_item) getConfig();};
/// Allocate and initialize the LED strip (FastLED / Adafruit_NeoPixel).
int Setup() override;
/// Turn off the strip and free the LED buffer.
int Stop() override;
/// Channel type derived from the LED strip type (RGB or RGBW).
int getChanType() override;
//int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override;
//int Ctrl(itemCmd cmd, char* subItem=NULL) override;
/// Set the LED range (or the whole strip) to ON/OFF or an RGB color and show the result.
int PixelCtrl(itemCmd cmd, char* subItem=NULL, bool show=true, bool authorized = false ) override;
//int PixelCtrl(itemCmd cmd, int from =0 , int to = 1024, bool show = 1) override;
/// Number of LEDs on the strip.
int numLeds;
/// Data pin of the strip.
int8_t pin;
/// LED data order / speed flags.
int ledsType;
protected:
/// Read data pin, LED count and LED type from the item arguments.
void getConfig();
};
#endif
+92 -16
View File
@@ -6,10 +6,14 @@
#include <abstractout.h>
#include <item.h>
/** Time (ms) to wait for the drainage feedback before assuming the tank is empty. */
#define DRENAGE_ON_TIME 10000
/** Maximum time (ms) allowed to refill the tank (vin valve) before a refill fault. */
#define VIN_MAX_TIME 1200000UL
/** Maximum time (ms) allowed to drain the tank before a drainage fault. */
#define DRENAGE_MAX_TIME 4000000UL
/** Bit masks of the last published digital states, kept in "@lastVals". */
#define LASTWCTRLSTATE 1
#define LASTWCTRLSTATE_ALL 2
#define LASTWMAXSTATE 4
@@ -23,30 +27,83 @@
#define LASTSTATE_MASK 0x3FF
#define LASTSTATE_INVALID 1024
/**
* @brief Water controller (sprinkler) state machine states.
*/
enum sprinklerState {
SP_UNKNOWN = 0,
SP_OFF = 1,
SP_DREN_ON = 2,
SP_DREN_OPERATE = 3,
SP_DREN_EMPTY = 4,
SP_VIN = 5,
SP_FULL = 6,
SP_DRYING = 7,
SP_FAULT_VIN = -1,
SP_FAULT_DREN = -2
SP_UNKNOWN = 0, ///< Initial state, no information about the tank yet.
SP_OFF = 1, ///< System off, valves closed, waiting to start drainage.
SP_DREN_ON = 2, ///< Drainage valve opened, waiting for the drainage feedback.
SP_DREN_OPERATE = 3,///< Drainage in progress, tank water level dropping.
SP_DREN_EMPTY = 4, ///< Tank drained (empty), waiting to refill.
SP_VIN = 5, ///< Tank refill in progress (vin valve open).
SP_FULL = 6, ///< Tank is full, watering may proceed.
SP_DRYING = 7, ///< Drying mode: pump runs, no zone watering.
SP_FAULT_VIN = -1, ///< Fault: tank refill (vin) did not complete in time.
SP_FAULT_DREN = -2 ///< Fault: drainage did not complete in time.
};
/**
* @brief Water tank / sprinkler controller driver (CH_SPRINKLER = 23).
*
* Controls a water system: drainage (dren), refill (vin), pump and a
* set of watering zones (valves), running a tank state machine
* (see sprinklerState) based on the water level and feedback inputs.
*
* JSON config (item argument array):
* @code
* [23, {
* "": { // root config object
* "vIn": pin, // refill valve output
* "rDren": pin, // drainage valve output
* "rPump": pin, // pump output
* "wMax": pin, // tank "full" level input
* "wMin": pin, // tank "low" level input
* "fbDren": pin, // drainage (water flow) feedback input
* "fbPump": pin, // pump feedback input
* "wCtr": pin, // water flow counter input
* "onFault": execObj // optional object executed on fault
* },
* "zone1": { "pin": pin }, // zone valve outputs (repeated per zone)
* "zone2": { "pin": pin }
* }]
* @endcode
* Each zone automatically gets "cmd", "val", "set" and "@active"
* members maintained by the driver.
*
* Control (Ctrl) commands:
* - /cmd ON / OFF / DRY / RESET (RESET clears all zone counters);
* - /val < 0 - sets the FREEZE flag (emergency stop, all valves close);
* - /set - water counter target value;
* - zone-scoped (subItem = zone name): /cmd ON/OFF/RESET, /set, /val.
*
* Published status (Poll):
* - $wMax, $wMin, $fbDren, $fbPump - tank/feedback input states (ON/OFF);
* - $rPump, $rDren, $rVIN - relay output states (ON/OFF);
* - $state - state machine state: OFF / DREN_ON / DREN_OPERATE /
* DREN_EMPTY / VIN / FULL / DRYING / FAULT_VIN / FAULT_DREN / UNKNOWN;
* - $fault - fault code: 0 = ok, 1 = vin (refill) fault, 2 = dren fault
* (or forwarded to the "onFault" object if configured);
* - /<zone>/$state - per-zone valve state (ON/OFF).
*/
class out_sprinkler : public abstractOut {
public:
//out_sprinkler(){ /*NO getConfig() here due Poll() optimization*/ };
/// Parse the config object: root pins and the zone (gates) object.
bool getConfig();
void link(Item * _item){abstractOut::link(_item); if (_item) getConfig();};
/// Attach to an item and parse its configuration.
void link(Item * _item){abstractOut::link(_item); if (_item) getConfig();};
/// Initialize output/input pins and per-zone state entries.
int Setup() override;
/// Run the tank state machine, publish input states, water the next zone, control the pump.
int Poll(short cause) override;
/// Close all zones, valves and the pump, mark the driver uninitialized.
int Stop() override;
//int Status() override;
//int Status() override;
/// Channel type of the driver.
int getChanType() override;
/// Handle controller /cmd (ON/OFF/DRY/RESET), /val (freeze) and /set, plus per-zone commands.
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
protected:
@@ -55,24 +112,43 @@ protected:
short wMaxPin, wMinPin, fbDrenPin, fbPumpPin, wCtrPin;
/// Switch the pump on/off and publish $rPump on change.
void pump(bool state);
/// Switch the drainage valve on/off and publish $rDren on change.
void dren(bool state);
/// Switch the refill (vin) valve on/off and publish $rVIN on change.
void vin(bool state);
/// Drive an output pin (negative pin = inverted).
void setOutput(short pin, bool value);
/// True when the pump is required by an active/queued zone.
bool isNeedPump(bool steelNeed=false);
void turnOffValves();
/// Close all zone valves plus the vin and dren valves.
void turnOffValves();
/// Close the valves of all zones.
void turnOffAllZones();
/// Get a zone object by its name from the gates object.
aJsonObject * getZone(const char * name);
/// Find the next zone that must be watered (active, ON with val<set, or ON without set).
aJsonObject * findNextZone();
/// Mark a zone active/inactive and publish its /<zone>/$state.
void setZoneActive(aJsonObject * zone, bool active);
/// Increment a zone counter ("val") by value and publish the new value.
void updateZoneValue(aJsonObject * zone, long value);
/// Publish a boolean state as ON/OFF on the given subtopic.
bool publishBooleanState(const char * subItem, bool state);
/// Publish a numeric state on the given subtopic.
void publishNumericState(const char * subItem, long value);
/// Publish a boolean state only when it changed since the last publication.
bool publishBooleanStateIfChanged(const char * subItem, bool state, uint32_t flag, uint32_t & lastState, bool forcePublish=false);
/// True when the item is in the freeze (emergency stop) state.
bool isFreeze();
void notifyState(short state);
/// Store, publish ($state, $fault) and notify the onFault object about a state change.
void notifyState(short state);
/// Apply the physical outputs for the given state machine state and notify about it.
int moveToState(sprinklerState nextState);
void updateCounterValue();
void getAndNotifyStatus();
/// Increment the global water counter ("@wCtr") and publish it.
void updateCounterValue();
/// Republish the current state machine status.
void getAndNotifyStatus();
};
#endif
+39
View File
@@ -40,6 +40,12 @@
#define MODULE_UATRBRIDGE_UARTB_TX_PIN -1
#endif
/**
* @brief Persistent state of the UART bridge driver.
*
* Stores the UART configuration (baud rate and serial format) restored
* after reboot.
*/
class ubPersistent : public chPersistent {
public:
@@ -53,27 +59,60 @@ public:
//aJsonObject * parameters;
};
/** Timeout (ms) of inactivity used to split the byte stream into PDUs. */
#define PDELAY 10
/**
* @brief Two-way UART bridge driver (CH_UARTBRIDGE = 15).
*
* Transparently bridges two fixed hardware UARTs
* (MODULE_UATRBRIDGE_UARTA / MODULE_UATRBRIDGE_UARTB, Serial1/Serial2
* by default): every byte received on A is forwarded to B and vice
* versa. The byte stream is split into PDUs of at most MAX_PDU (64)
* bytes using PDELAY inactivity timeout, '*' or '\r' boundaries;
* each PDU is reported as a UDP datagram to ip:port (default port
* 5555, source ports 5551/5552 for directions A/B).
*
* JSON config (item argument object):
* @code
* [15, {
* "serial": "8N1", // serial format, default 8N1
* "baud": 9600, // baud rate, default 9600
* "ip": "1.2.3.4", // optional UDP dump destination
* "port": 5555, // optional UDP dump destination port
* "hd": 1, // half-duplex mode (flush opposite direction)
* "log": 0, // log level: 0 off, 1 hex, 2 printable
* "bond": "string" // optional PDU boundary characters
* }]
* @endcode
*/
class out_UARTbridge : public abstractOut {
public:
// out_UARTbridge(Item * _item):abstractOut(_item){store = (ubPersistent *) item->getPersistent();};
/// Default constructor.
out_UARTbridge():store(NULL){};
/// Attach to an item and grab its persistent state.
void link(Item * _item){abstractOut::link(_item); if (_item) {store = (ubPersistent *) item->getPersistent();} else store = NULL;};
/// Allocate persistent state, load config and start both UARTs.
int Setup() override;
/// Pump bytes in both directions, split PDUs and report them via UDP.
int Poll(short cause) override;
/// Stop the UDP dump sockets and free the persistent state.
int Stop() override;
/// Channel type of the driver.
int getChanType() override;
/// Control commands; currently only ON/OFF are accepted (no-op).
int Ctrl(itemCmd cmd, char* subItem=NULL, bool toExecute=true, bool authorized = false) override;
//int getDefaultStorageType(){return ST_INT32;};
//int Ctrl(short cmd, short n=0, int * Parameters=NULL, int suffixCode=0, char* subItem=NULL) override;
protected:
ubPersistent * store;
/// Read the JSON config object (serial/baud/ip/port/hd/log/bond) and start the UARTs.
bool getConfig();
// int findRegister(int registerNum, int posInBuffer, int regType);
// void pollModbus(aJsonObject * reg, int regType);
+53 -44
View File
@@ -1,15 +1,17 @@
#pragma once
#include <Arduino.h>
// PID: do not filter (scale/clamp) the regulator output
#ifndef NOT_FILTER_PID_OUT
#define NOT_FILTER_PID_OUT 1
#endif
#define DHCP_ATTEMPTS_FALLBACK 3
#define TENS_FRACT_LEN 2
#define TENS_BASE 100
#define DHCP_ATTEMPTS_FALLBACK 3 // DHCP attempts before falling back to static IP
#define TENS_FRACT_LEN 2 // fraction digits of "tens" fixed-point values
#define TENS_BASE 100 // scale factor of "tens" fixed-point values
#define DEFAULT_FILESIZE_LIMIT 65535
#define DEFAULT_FILESIZE_LIMIT 65535 // max download size in bytes
// Max JSON config size, per platform
#ifndef MAX_JSON_CONF_SIZE
#if defined(__SAM3X8E__)
@@ -34,11 +36,11 @@
#define SYSLOG_LOCAL_SOCKET 514
#ifndef MODBUS_UART_RX_PIN
#define MODBUS_UART_RX_PIN -1
#define MODBUS_UART_RX_PIN -1 // UART RX pin for the Modbus RS485 bus
#endif
#ifndef MODBUS_UART_TX_PIN
#define MODBUS_UART_TX_PIN -1
#define MODBUS_UART_TX_PIN -1 // UART TX pin for the Modbus RS485 bus
#endif
#ifndef FASTLED
@@ -49,7 +51,7 @@
// If not defined ADAFRUIT_LED - FastLED library will be used instead
// And strip type, pin, order must defined on compilation time
#ifndef CONTROLLER
#define CONTROLLER TM1809
#define CONTROLLER TM1809 // Default LED strip controller type
#endif
#ifndef SCALE_VOLUME_100
@@ -57,13 +59,14 @@
#endif
#ifndef DATA_PIN
#define DATA_PIN 4
#define DATA_PIN 4 // LED strip data (DIN) pin
#endif
#ifndef ORDER
#define ORDER BRG
#define ORDER BRG // LED strip color order
#endif
// RS485 transceiver direction (DE/RE) control pin
#ifndef MODBUS_TX_PIN
#define TXEnablePin 13
#else
@@ -73,39 +76,39 @@
//#define ESP_EEPROM_SIZE 2048
#ifndef AVR_DMXOUT_PIN
#define AVR_DMXOUT_PIN 18
#define AVR_DMXOUT_PIN 18 // Software DMX output pin on AVR
#endif
#define WIFI_TIMEOUT 60000UL
#define TIMEOUT_RECONNECT 10000UL
#define TIMEOUT_REINIT 5000UL
#define TIMEOUT_RELOAD 30000UL
#define TIMEOUT_RETAIN 8000UL
#define TIMEOUT_REINIT_NOT_CONFIGURED 120000UL
#define INTERVAL_1W 5000UL
#define WIFI_TIMEOUT 60000UL // Max time waiting for a WiFi connection
#define TIMEOUT_RECONNECT 10000UL // Delay before retrying an MQTT reconnect
#define TIMEOUT_REINIT 5000UL // Delay before re-initializing the LAN
#define TIMEOUT_RELOAD 30000UL // Delay before reloading the config
#define TIMEOUT_RETAIN 8000UL // Delay while publishing retained states
#define TIMEOUT_REINIT_NOT_CONFIGURED 120000UL // Longer re-init when not yet configured
#define INTERVAL_1W 5000UL // 1-Wire temperature poll interval (ms)
#define PERIOD_THERMOSTAT_FAILED (600 * 1000UL) //16000 sec (4h) max
//#define T_ATTEMPTS 200
//#define IET_TEMP 0
//#define IET_ATTEMPTS 1
#ifndef THERMO_GIST_CELSIUS
#define THERMO_GIST_CELSIUS 1.
#define THERMO_GIST_CELSIUS 1. // Thermostat hysteresis / dead-band (deg C)
#endif
#ifndef THERMO_OVERHEAT_CELSIUS
#define THERMO_OVERHEAT_CELSIUS 38.
#define THERMO_OVERHEAT_CELSIUS 38. // Thermostat overheat alarm threshold
#endif
#ifndef FM_OVERHEAT_CELSIUS
#define FM_OVERHEAT_CELSIUS 40.
#define FM_OVERHEAT_CELSIUS 40. // Fan/AC overheat alarm threshold
#endif
#ifndef MIN_VOLUME
#define MIN_VOLUME 20
#define MIN_VOLUME 20 // Minimum LED strip brightness
#endif
#ifndef INIT_VOLUME
#define INIT_VOLUME 30
#define INIT_VOLUME 30 // Initial LED strip brightness on power-up
#endif
/*
@@ -129,34 +132,34 @@
*/
#ifndef INTERVAL_CHECK_INPUT
#define INTERVAL_CHECK_INPUT 11
#define INTERVAL_CHECK_INPUT 11 // ms between digital input scans
#endif
#define INTERVAL_CHECK_ULTRASONIC 100
#define INTERVAL_CHECK_ULTRASONIC 100 // ms between ultrasonic sensor reads
#ifndef TIMER_CHECK_INPUT
#define TIMER_CHECK_INPUT 15
#define TIMER_CHECK_INPUT 15 // ms between timer (pulse-count) input scans
#endif
#ifndef INTERVAL_CHECK_SENSOR
#define INTERVAL_CHECK_SENSOR 5000
#define INTERVAL_CHECK_SENSOR 5000 // ms between periodic sensor polls
#endif
#define INTERVAL_SLOW_POLLING 1000
#define INTERVAL_SLOW_POLLING 1000 // ms between slow item polls
//#define INTERVAL_POLLING 100
#ifndef THERMOSTAT_CHECK_PERIOD
#define THERMOSTAT_CHECK_PERIOD 30000
#define THERMOSTAT_CHECK_PERIOD 30000 // ms between thermostat control checks
#endif
#ifndef OW_UPDATE_INTERVAL
#define OW_UPDATE_INTERVAL 5000
#define OW_UPDATE_INTERVAL 5000 // ms between 1-Wire update cycles
#endif
#ifndef MODBUS_SERIAL_BAUD
#define MODBUS_SERIAL_BAUD 9600
#define MODBUS_SERIAL_BAUD 9600 // Modbus RS485 baud rate
#endif
#ifndef MODBUS_SERIAL_PARAM
#define MODBUS_SERIAL_PARAM SERIAL_8N1
#define MODBUS_SERIAL_PARAM SERIAL_8N1 // Modbus RS485 framing
#endif
/*
@@ -169,17 +172,19 @@
#endif
*/
#define MODBUS_FM_BAUD 9600
#define MODBUS_FM_PARAM SERIAL_8N1
#define MODBUS_FM_BAUD 9600 // VACOM/FM Modbus device baud rate
#define MODBUS_FM_PARAM SERIAL_8N1 // VACOM/FM Modbus device framing
#ifndef SERIAL_BAUD
#define SERIAL_BAUD 115200
#define SERIAL_BAUD 115200 // Debug serial baud rate
#endif
// Default MAC used when the chip provides no unique ID
#ifndef CUSTOM_FIRMWARE_MAC
#define DEFAULT_FIRMWARE_MAC {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xFF}
#endif
// Remote configuration server (MY_CONFIG_SERVER overrides the default)
#ifndef MY_CONFIG_SERVER
#define CONFIG_SERVER "lazyhome.ru"
#else
@@ -187,7 +192,7 @@
#endif
#ifndef HOMETOPIC
#define HOMETOPIC "myhome"
#define HOMETOPIC "myhome" // Root of all MQTT topics
#endif
//Default output topic
@@ -205,9 +210,10 @@
#define INTOPIC "in"
#endif
#define MQTT_SUBJECT_LENGTH 20
#define MQTT_TOPIC_LENGTH 64
#define MQTT_SUBJECT_LENGTH 20 // Max length of an MQTT subject name
#define MQTT_TOPIC_LENGTH 64 // Max length of a full MQTT topic
// Feature enable/disable toggles
#ifndef DMX_DISABLE
#define _dmxin
#define _dmxout
@@ -226,10 +232,10 @@
#endif
#ifndef LAN_INIT_DELAY
#define LAN_INIT_DELAY 500
#define LAN_INIT_DELAY 500 // ms to wait before starting LAN init
#endif
#define DEFAULT_INC_STEP 5
#define DEFAULT_INC_STEP 5 // Default step for INCREASE/DECREASE commands
#if defined(ARDUINO_ARCH_AVR)
@@ -323,15 +329,15 @@
#endif
#endif
#define DHT_POLL_DELAY_DEFAULT 15000
#define UPTIME_POLL_DELAY_DEFAULT 30000
#define DHT_POLL_DELAY_DEFAULT 15000 // Default DHT sensor poll interval (ms)
#define UPTIME_POLL_DELAY_DEFAULT 30000 // Default uptime stats publish interval (ms)
#ifdef ARDUINO_ARCH_STM32F1
#define strncpy_P strncpy
#endif
#ifndef DMX_SMOOTH_DELAY
#define DMX_SMOOTH_DELAY 10
#define DMX_SMOOTH_DELAY 10 // ms between DMX output smoothing steps
#endif
//#ifdef M5STACK
@@ -381,28 +387,31 @@
#define isAnalogPin(p) ((p >= 14) && (p<=21))
#endif
// Minimum free RAM (bytes) required before a heavy operation
#ifdef AVR
#define minimalMemory 200
#else
#define minimalMemory 1200
#endif
// Comma-separated list of GPIO pins protected from raw control
#if not defined PROTECTED_PINS
#define PROTECTED_PINS
#endif
#if not defined DEFAULT_OTA_USERNAME
#define DEFAULT_OTA_USERNAME arduino
#define DEFAULT_OTA_USERNAME arduino // Default OTA update login
#endif
#if not defined DEFAULT_OTA_PASSWORD
#define DEFAULT_OTA_PASSWORD password
#define DEFAULT_OTA_PASSWORD password // Default OTA update password
#endif
const short protectedPins[]={PROTECTED_PINS};
#define protectedPinsNum (sizeof(protectedPins)/sizeof(short))
#ifdef CRYPT
// Shared secret used for config encryption
#if not defined SHAREDSECRET
#define SHAREDSECRET "12345678"
#endif
+32
View File
@@ -19,6 +19,15 @@ e-mail anklimov@gmail.com
*/
//define APU_OFF
/**
* @file owTerm.h
* @brief 1-Wire temperature sensing for thermostats.
*
* Reads DS18S20/DS18B20 digital thermometers (device type 0x28) through
* a DS2482-100 I2C-to-1-Wire bridge (or a software 1-Wire driver on a
* dedicated pin). Each configured sensor is polled periodically and its
* temperature is published to the item's /val subtopic.
*/
#pragma once
#define SW_AUX0 0x40
@@ -61,6 +70,7 @@ e-mail anklimov@gmail.com
#include <DallasTemperature.h>
#include "aJSON.h"
/** @brief 1-Wire sensor configuration array from the JSON config. */
extern aJsonObject *owArr;
//typedef void (*owChangedType) (int , DeviceAddress, float) ;
@@ -78,13 +88,35 @@ extern aJsonObject *owArr;
#endif
/** @brief 1-Wire bus handle (DS2482 bridge or software pin driver). */
extern OneWire *oneWire;
/** @brief DallasTemperature manager on top of the 1-Wire bus. */
extern DallasTemperature *sensors;
/**
* @brief Scans the 1-Wire bus and logs the devices it finds.
* @return true on success.
*/
int owUpdate();
/**
* @brief Initializes the 1-Wire bus and the temperature sensors, and
* applies the configured resolution.
* @return true on success.
*/
int owSetup();
/**
* @brief Periodic poll: reads each configured sensor and publishes its
* temperature to the item's /val subtopic.
*/
void owLoop();
/**
* @brief Registers an idle callback invoked between 1-Wire operations.
* @param ptr Function to call while the bus is idle.
*/
void setupOwIdle(void (*)()) ;
/**
* @brief Triggers a 1-Wire device search/update.
*/
void owSearch();
#endif
+39
View File
@@ -20,14 +20,22 @@ e-mail anklimov@gmail.com
#pragma once
#include <Arduino.h>
/** @brief LED pattern bit: red component on. */
#define ledRED 1
/** @brief LED pattern bit: green component on. */
#define ledGREEN 2
/** @brief LED pattern bit: blue component on. */
#define ledBLUE 4
/** @brief LED pattern bit: blink at the normal rate. */
#define ledBLINK 8
/** @brief LED pattern bit: blink at the fast rate. */
#define ledFASTBLINK 16
/** @brief Mask of all steady/blink pattern bits. */
#define ledParams (ledRED | ledGREEN | ledBLUE | ledBLINK | ledFASTBLINK)
/** @brief Internal flag: show the pattern once (flash). */
#define ledFlash 32
/** @brief Internal flag: LED is currently in the "hidden" (off) blink phase. */
#define ledHidden 64
#if defined(ARDUINO_ARCH_AVR)
@@ -35,22 +43,53 @@ e-mail anklimov@gmail.com
#define pinGREEN 48
#define pinBLUE 49
#else
// Due: RGB status LED on pins 50-52
#define pinRED 50
#define pinGREEN 51
#define pinBLUE 52
#endif
/** @brief Blink period in milliseconds (normal rate). */
#define ledDelayms 1000UL
/** @brief Blink period in milliseconds (fast rate). */
#define ledFastDelayms 300UL
/**
* @brief RGB status LED controller.
*
* Drives the onboard RGB status LED (Due: pins 50-52, AVR: 47-49) with
* color patterns, single flashes and slow/fast blinking. Used by the
* system to signal state (e.g. errors blink red).
*/
class StatusLED {
public:
/**
* @brief Initializes the LED pins and sets the initial pattern.
* @param pattern Initial LED pattern bits.
*/
StatusLED(uint8_t pattern = 0);
/**
* @brief Sets the steady/blinking LED pattern.
* @param pattern LED pattern bits.
*/
void set (uint8_t pattern);
/**
* @brief Immediately drives the pins to the given color (no state change).
* @param pattern LED pattern bits.
*/
void show (uint8_t pattern);
/**
* @brief Advances blink/flash timing; call from the main loop.
*/
void poll();
/**
* @brief Shows the given pattern once (single flash).
* @param pattern LED pattern bits to flash.
*/
void flash(uint8_t pattern);
private:
/** @brief Current pattern including internal state flags. */
uint8_t curStat;
/** @brief millis() timestamp of the last timing step. */
uint32_t timestamp;
};
+21
View File
@@ -1,12 +1,28 @@
/**
* @file systemconfigdata.h
* @brief Fixed-layout binary system configuration block stored in flash.
*
* Defines the on-flash layout (systemConfigData) that the systemConfig
* manager reads/writes: signature, MAC, config flags, static network
* addresses, config server URL, passwords and the remote-config ETag.
*/
#pragma once
/** @brief Offset of the system config block in the storage. */
#define SYSCONF_OFFSET 0
/** @brief End offset of the fixed (unaligned) config block. */
#define EEPROM_offset_NotAlligned SYSCONF_OFFSET+sizeof(systemConfigData)
/** @brief Total size of the fixed config area. */
#define SYSCONF_SIZE EEPROM_offsetJSON
/** @brief 4-byte aligned offset where the JSON config storage starts. */
#define EEPROM_offsetJSON EEPROM_offset_NotAlligned + (4 -(EEPROM_offset_NotAlligned & 3))
/** @brief Max length of flash-stored strings (URLs, ETag). */
#define MAXFLASHSTR 32
/** @brief Max length of flash-stored passwords. */
#define PWDFLASHSTR 16
/** @brief Signature marking a valid config block. */
#define EEPROM_SIGNATURE "LHC3"
/** @brief Length of the config block signature. */
#define EEPROM_SIGNATURE_LENGTH 4
//#define EEPROM_offsetJSON IFLASH_PAGE_SIZE
@@ -14,11 +30,15 @@
const char EEPROM_signature[] = EEPROM_SIGNATURE;
/** @brief Fixed-size flash string buffer. */
typedef char flashstr[MAXFLASHSTR];
/** @brief Fixed-size flash password buffer. */
typedef char flashpwd[PWDFLASHSTR];
/** @brief 6-byte MAC address. */
typedef uint8_t macAddress[6];
#pragma pack(push, 1)
/** @brief Packed 32-bit system configuration flags. */
typedef union
{
uint32_t configFlags32bit;
@@ -36,6 +56,7 @@ const char EEPROM_signature[] = EEPROM_SIGNATURE;
};
} systemConfigFlags;
/** @brief Fixed-layout system configuration block persisted in flash. */
typedef struct
{
char signature[4];
+7
View File
@@ -1,3 +1,10 @@
/**
* @file textconst.h
* @brief Topic/suffix string constants.
*
* PROGMEM string constants for MQTT topics, Homie metadata, command
* names and item subtopic suffixes used across the controller.
*/
#pragma once
const char state_P[] PROGMEM = "$state";
+76
View File
@@ -17,6 +17,15 @@ GIT: https://github.com/anklimov/lighthub
e-mail anklimov@gmail.com
*/
/**
* @file utils.h
* @brief Utility helpers of LightHub.
*
* Miscellaneous platform and protocol helpers: JSON (aJSON) getters and
* object creation, command execution (executeCommand), MQTT topic building
* (setTopic), number/byte/string parsing, IP address utilities, CRC16 of
* JSON objects, pin access and logging.
*/
#pragma once
#define Q(x) #x
@@ -37,6 +46,11 @@ using namespace ios;
#include "Streaming.h"
#endif
/**
* MQTT topic prefix types used by setTopic():
* T_ROOT - root topic only, T_DEV - device topic, T_BCST - broadcast
* (incoming) topic, T_OUT - output (status) topic.
*/
enum topicType {
T_ROOT = 4,
T_DEV = 1,
@@ -44,55 +58,109 @@ enum topicType {
T_OUT = 3
};
/// Type for a serial parameter (uint32 on ESP32, uint16 elsewhere)
#if defined(ESP32)
#define serialParamType uint32_t
#else
#define serialParamType uint16_t
#endif
/** Print `count` bytes from `addr` to the debug serial as hex. */
void PrintBytes(uint8_t* addr, uint8_t count, bool newline);
/** Format `count` bytes from `addr` as a hex string into `out`. */
void SetBytes(uint8_t* addr, uint8_t count, char * out);
/** Parse a hex string (AA:BB:...) into a MAC/EUI byte buffer. @return true on success. */
bool SetAddr(char * in, uint8_t* addr);
/** Convert one hex digit to decimal, setting `err` on failure. */
uint8_t HEX2DEC(char i, bool* err);
/** Parse an integer from `*chan`, advancing the pointer past it (after ','). */
long getIntFromStr(char **chan);
/** Parse a typed number (int / tens / float) from `*chan` into an itemCmd. */
itemCmd getNumber(char ** chan);
/** @return current free heap size in bytes. */
unsigned long freeRam ();
/** Parse a delimited hex string into a byte array. */
void parseBytes(const char* str, char separator, byte* bytes, int maxBytes, int base);
/** Log a formatted message to the debug serial. */
int log(const char *str, ...);
/** Convert a float to a fixed-format string for MQTT publishing. */
void printFloatValueToStr(char *valstr, float value);
/** Read the chip unique id (MAC) into 32-bit words. @return the id. */
uint32_t ReadUniqueID( uint32_t * pdwUniqueID );
/** Convert an IP address string to an IPAddress. @return true on success. */
int _inet_aton(const char* aIPAddrString, IPAddress& aResult);
/** Convert an IPAddress to a string in `buf`. */
char *_inet_ntoa_r(IPAddress addr, char *buf, int buflen);
/** Print an IP address to the debug serial. */
void printIPAddress(IPAddress ipAddress);
/** Build the full MQTT topic prefix of the given type in `buf`. @return buf. */
char* setTopic(char* buf, int8_t buflen, topicType tt, const char* suffix = NULL);
/** Convert an unsigned long to a string for MQTT publishing. */
void printUlongValueToStr(char *valstr, unsigned long value);
/** Scan the I2C bus and log the found devices. */
void scan_i2c_bus();
/** Perform a soft reboot of the device. */
void softRebootFunc();
/** @return true if (currTime - timestamp) >= time, with wrap-around safety. */
bool isTimeOver(uint32_t timestamp, uint32_t currTime, uint32_t time, uint32_t modulo = 0);
//bool executeCommand(aJsonObject* cmd, int8_t toggle = -1, char* defCmd = NULL);
/**
* Execute a JSON command object with an empty itemCmd.
* @param toggle - 0: use icmd/ecmd, 1: use irev/erev, -1: no toggle
*/
bool executeCommand(aJsonObject* cmd, int8_t toggle = -1);
/**
* @brief Execute a JSON command object: control an item (Ctrl), publish
* an emit topic (MQTT) and/or send a CAN command.
* @param cmd - command object: array (iterate), object with item/emit/can/
* icmd/irev/ecmd/erev/act fields, or a legacy string
* @param toggle - 0: use icmd/ecmd, 1: use irev/erev, -1: no toggle
* @param _itemCmd - itemCmd value to send when no command string is given
* @param defaultItem/defaultEmit/defaultCan - fallback targets
*/
bool executeCommand(aJsonObject* cmd, int8_t toggle, itemCmd _itemCmd, aJsonObject* defaultItem=NULL, aJsonObject* defaultEmit=NULL, aJsonObject* defaultCan = NULL);
/** Map a number through a JSON map into an itemCmd. */
itemCmd mapInt(int32_t arg, aJsonObject* map);
/** @return millis() shifted and masked, never returning 0 or -1 (for timestamp storage). */
uint32_t millisNZ(uint8_t shift=0, uint32_t mask=0xFFFFFFFFUL);
/** Parse a serial parameter string (e.g. baud rate / parity) to its code. */
serialParamType str2SerialParam(char * str);
/** @return an IPAddress as a string. */
String toString(const IPAddress& address);
/** @return the current output state of a pin (fast register read). */
bool getPinVal(uint8_t pin);
/** Parse a register size string (Modbus). */
int str2regSize(char * str);
/** Validate an authorization token against data. @return true if the token is valid. */
bool checkToken(char * token, char * data);
/** @return true if the pin is in the protected pins list (arming timer applies). */
bool isProtectedPin(short pin);
/** Recover the I2C bus by toggling SCL. @return true on success. */
bool i2cReset();
/** @return CRC16 of a JSON object (config integrity, CANDRV builds). */
uint16_t getCRC(aJsonObject * in);
/** @return the string element of a JSON array by index, or NULL. */
char* getStringFromJson(aJsonObject * a, int i);
/** @return the string field of a JSON object by name, or NULL. */
char* getStringFromJson(aJsonObject * a, const char * name);
/** @return the int element of a JSON array by index, or `def`. */
long getIntFromJson(aJsonObject * a, int i, long def = 0);
/** @return the int field of a JSON object by name, or `def`. */
long getIntFromJson(aJsonObject * a, const char * name, long def = 0);
/** @return the float element of a JSON array by index, or `def`. */
float getFloatFromJson(aJsonObject * a, int i, float def = 0.0);
/** @return the float field of a JSON object by name, or `def`. */
float getFloatFromJson(aJsonObject * a, const char * name, float def = 0.0);
/** @return the int field of a JSON object by name and array index, or `def`. */
long getIntFromJson(aJsonObject * a, const char * name, int i,long def);
/** @return the float field of a JSON object by name and array index, or `def`. */
float getFloatFromJson(aJsonObject * a, const char * name, int i, float def);
/** Configure a pin as an input. */
void setupInPin(short pin);
/** @return the (debounced) value of an input pin. */
bool readInPin(short pin);
/** Write a value to an output pin. */
void writeOutPin(short pin, bool val);
// Get object from array, create if absent and return pointer to object
@@ -193,11 +261,19 @@ return element;
#ifdef CANDRV
#include "util/crc16_.h"
/**
* @brief Print/Stream sink accumulating a CRC16 over the written bytes.
*
* Used together with aJson printing to compute the CRC of a config object
* (CANDRV builds).
*/
class CRCStream : public Stream
{
public:
CRCStream() : CRC16(0xFFFF){}
/// Running CRC16 value
uint16_t CRC16;
/** @return the accumulated CRC16. */
uint16_t getCRC16() {return CRC16;}
// Stream methods