Instrument Neutral Distributed Interface INDI 2.2.5
waveshare_modbus_relay.cpp
Go to the documentation of this file.
1/*
2 Waveshare ModBUS POE Output
3 Copyright (C) 2024 Jasem Mutlaq
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Lesser General Public License for more details.
12 You should have received a copy of the GNU Lesser General Public
13 License along with this library; if not, write to the Free Software
14 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15*/
16
18#include "../../libs/modbus/platform.h"
19
20#include <memory>
21#include <unistd.h>
22#include <iomanip>
23#include <sstream>
25
26static std::unique_ptr<WaveshareRelay> sesto(new WaveshareRelay());
27
28WaveshareRelay::WaveshareRelay() : OutputInterface(this), InputInterface(this) // Added InputInterface(this)
29{
30 setVersion(1, 0);
31}
32
37{
39
40 INDI::OutputInterface::initProperties("Relays", 8, "Output");
41 INDI::InputInterface::initProperties("Digital Inputs", 8, 0, "Input");
42
44
46
48 tcpConnection = new Connection::TCP(this);
49 tcpConnection->setDefaultHost("192.168.1.1");
50 tcpConnection->setDefaultPort(502);
51 tcpConnection->registerHandshake([&]()
52 {
53 return Handshake();
54 });
55
56 registerConnection(tcpConnection);
57
58 FirmwareVersionTP[0].fill("VERSION", "Version", "1.00");
59 FirmwareVersionTP.fill(getDeviceName(), "FIRMWARE", "Firmware", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
60
61 return true;
62}
63
68{
71
72 if (m_HaveInput)
74
75 if (isConnected())
76 {
77 defineProperty(FirmwareVersionTP);
79 }
80 else
81 {
82 deleteProperty(FirmwareVersionTP);
83 }
84
85 return true;
86}
87
92{
93 PortFD = tcpConnection->getPortFD();
94
95 nmbs_platform_conf platform_conf;
96 platform_conf.transport = NMBS_TRANSPORT_TCP;
97 platform_conf.read = read_fd_linux;
98 platform_conf.write = write_fd_linux;
99 platform_conf.arg = &PortFD;
100
101 // Create the modbus client
102 nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
103 if (err != NMBS_ERROR_NONE)
104 {
105 LOGF_ERROR("Error creating modbus client: %d", err);
106 if (!nmbs_error_is_exception(err))
107 return false;
108 }
109
110 // Set only the response timeout. Byte timeout will be handled by the TCP connection
111 nmbs_set_read_timeout(&nmbs, 1000);
112
113 // Check if we have input support
114 {
115 nmbs_bitfield inputs_buffer = {0};
116 m_HaveInput = nmbs_read_discrete_inputs(&nmbs, 0, 8, inputs_buffer) == NMBS_ERROR_NONE;
117 if (m_HaveInput)
118 {
121 }
122 }
123
124 uint16_t output;
125 err = nmbs_read_holding_registers(&nmbs, 0x8000, 1, &output);
126 if (err == NMBS_ERROR_NONE)
127 {
128 std::stringstream ss;
129 ss << std::fixed << std::setprecision(2) << output / 100.0;
130 FirmwareVersionTP[0].setText(ss.str().c_str());
131 FirmwareVersionTP.setState(IPS_OK);
132 return true;
133 }
134
135 LOGF_ERROR("Failed to query device firmware version: %d", err);
136 return false;
137}
138
139
144{
145 return "Waveshare Relay";
146}
147
151bool WaveshareRelay::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
152{
153 // Check Output Properties
154 if (INDI::OutputInterface::processText(dev, name, texts, names, n))
155 return true;
156
157 // Check Input Properties
158 if (m_HaveInput && INDI::InputInterface::processText(dev, name, texts, names, n))
159 return true;
160
161 return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
162}
163
167bool WaveshareRelay::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
168{
169 if (INDI::OutputInterface::processSwitch(dev, name, states, names, n))
170 return true;
171
172 return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
173}
174
175bool WaveshareRelay::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
176{
177 if (INDI::OutputInterface::processNumber(dev, name, values, names, n))
178 return true;
179
180 return INDI::DefaultDevice::ISNewNumber(dev, name, values, names, n);
181}
182
193
198{
199 if (!isConnected())
200 return;
201
202 if (m_HaveInput)
205
206
208}
209
214{
215 nmbs_bitfield coils = {0};
216 auto err = nmbs_read_coils(&nmbs, 0, 8, coils);
217 if (err != NMBS_ERROR_NONE)
218 {
219 LOGF_ERROR("Error reading coils at address 0: %s", nmbs_strerror(err));
220 return false;
221 }
222 else
223 {
224 for (size_t i = 0; i < DigitalOutputsSP.size(); i++)
225 {
226 auto oldState = DigitalOutputsSP[i].findOnSwitchIndex();
227 auto newState = nmbs_bitfield_read(coils, i);
228 if (oldState != newState)
229 {
230 DigitalOutputsSP[i].reset();
231 DigitalOutputsSP[i][newState].setState(ISS_ON);
232 DigitalOutputsSP[i].setState(IPS_OK);
233 DigitalOutputsSP[i].apply();
234 }
235 }
236 return true;
237 }
238}
239
243bool WaveshareRelay::CommandOutput(uint32_t index, OutputState command)
244{
245 uint16_t value = (command == OutputState::On) ? 0xFF00 : 0;
246
247 auto err = nmbs_write_single_coil(&nmbs, index, value);
248 if (err != NMBS_ERROR_NONE)
249 {
250 LOGF_ERROR("Error writing single coil at address %d: %s", index, nmbs_strerror(err));
251 return false;
252 }
253
254 return true;
255}
256
261{
262 nmbs_bitfield inputs_buffer = {0};
263
264 nmbs_error err = nmbs_read_discrete_inputs(&nmbs, 0, 8, inputs_buffer);
265
266 if (err != NMBS_ERROR_NONE)
267 {
268 LOGF_ERROR("Error reading discrete inputs at address 0: %s", nmbs_strerror(err));
269 for (int i = 0; i < 8; i++)
270 {
271 if (DigitalInputsSP[i].getState() != IPS_ALERT)
272 {
273 DigitalInputsSP[i].setState(IPS_ALERT);
274 DigitalInputsSP[i].apply();
275 }
276 }
277 return false;
278 }
279 else
280 {
281 for (int i = 0; i < 8; i++)
282 {
283 auto oldStateIndex = DigitalInputsSP[i].findOnSwitchIndex();
284 auto newState = nmbs_bitfield_read(inputs_buffer, i);
285
286 // Update if value changed or if the individual input was in ALERT
287 if (DigitalInputsSP[i].getState() == IPS_ALERT || oldStateIndex != newState)
288 {
289 DigitalInputsSP[i].reset();
290 DigitalInputsSP[i][newState].setState(ISS_ON);
291 DigitalInputsSP[i].setState(IPS_OK);
292 DigitalInputsSP[i].apply();
293 }
294 }
295
296 return true;
297 }
298}
299
304{
305 // We don't have analog inputs in this simulator
306 return true;
307}
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The TCP class manages connection with devices over the network via TCP/IP. Upon successful connection...
void setDefaultHost(const char *addressHost)
void setDefaultPort(uint32_t addressPort)
int getPortFD() const
bool isConnected() const
const char * getDeviceName() const
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
void setDefaultPollingPeriod(uint32_t msec)
setDefaultPollingPeriod Change the default polling period to call TimerHit() function in the driver.
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
uint32_t getPollingPeriod() const
getPollingPeriod Return the polling period.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
void syncDriverInfo()
syncDriverInfo sends the current driver information to the client.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process text properties.
std::vector< INDI::PropertySwitch > DigitalInputsSP
bool saveConfigItems(FILE *fp)
saveConfigItems save Filter Names in config file
void initProperties(const char *groupName, uint8_t digital, uint8_t analog, const std::string &digitalPrefix="Digital", const std::string &analogPrefix="Analog")
Initialize filter wheel properties. It is recommended to call this function within initProperties() o...
bool updateProperties()
updateProperties Defines or Delete properties based on default device connection status
bool processNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process number properties.
void initProperties(const char *groupName, uint8_t outputs, const std::string &prefix="Output")
Initialize filter wheel properties. It is recommended to call this function within initProperties() o...
bool processSwitch(const char *dev, const char *name, ISState states[], char *names[], int n)
Process switch properties.
bool saveConfigItems(FILE *fp)
saveConfigItems save Filter Names in config file
std::vector< INDI::PropertySwitch > DigitalOutputsSP
bool processText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process text properties.
bool updateProperties()
updateProperties Defines or Delete properties based on default device connection status
void setState(IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
Provides interface to implement Digital/Analog input functionality.
Provides interface to implement Digital Boolean Output (On/Off) functionality.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
const char * getDefaultName() override
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool UpdateAnalogInputs() override
Update all analog inputs.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool UpdateDigitalInputs() override
Update all digital inputs.
virtual bool CommandOutput(uint32_t index, OutputState command) override
Send command to relay.
virtual bool UpdateDigitalOutputs() override
Update all digital outputs.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
ISState
Switch state.
Definition indiapi.h:148
@ ISS_ON
Definition indiapi.h:150
@ IP_RO
Definition indiapi.h:182
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
nmbs_error nmbs_client_create(nmbs_t *nmbs, const nmbs_platform_conf *platform_conf)
nmbs_error nmbs_read_holding_registers(nmbs_t *nmbs, uint16_t address, uint16_t quantity, uint16_t *registers_out)
const char * nmbs_strerror(nmbs_error error)
nmbs_error nmbs_write_single_coil(nmbs_t *nmbs, uint16_t address, uint16_t value)
nmbs_error nmbs_read_coils(nmbs_t *nmbs, uint16_t address, uint16_t quantity, nmbs_bitfield coils_out)
void nmbs_set_read_timeout(nmbs_t *nmbs, int32_t timeout_ms)
Definition nanomodbus.c:196
nmbs_error nmbs_read_discrete_inputs(nmbs_t *nmbs, uint16_t address, uint16_t quantity, nmbs_bitfield inputs_out)
nmbs_error
Definition nanomodbus.h:54
@ NMBS_ERROR_NONE
Definition nanomodbus.h:64
uint8_t nmbs_bitfield[250]
Definition nanomodbus.h:84
#define nmbs_bitfield_read(bf, b)
Definition nanomodbus.h:94
@ NMBS_TRANSPORT_TCP
Definition nanomodbus.h:122
#define nmbs_error_is_exception(e)
Definition nanomodbus.h:78
int32_t read_fd_linux(uint8_t *buf, uint16_t count, int32_t timeout_ms, void *arg)
Definition platform.h:33
int32_t write_fd_linux(const uint8_t *buf, uint16_t count, int32_t timeout_ms, void *arg)
Definition platform.h:81
int32_t(* read)(uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg)
Definition nanomodbus.h:147
nmbs_transport transport
Definition nanomodbus.h:146
int32_t(* write)(const uint8_t *buf, uint16_t count, int32_t byte_timeout_ms, void *arg)
Definition nanomodbus.h:149