mirror of
https://github.com/anklimov/lighthub
synced 2026-10-28 11:15:00 +00:00
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:
@@ -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);
|
||||
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user