Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Waveshare Modbus Relay Driver #2085

Merged
merged 4 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion drivers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,11 @@
<device label="Terrans PowerBoxPro V2" manufacturer="Terrans Industry">
<driver name="Terrans PowerBoxPro V2">indi_terrans_powerboxpro_v2</driver>
<version>1.0</version>
</device>
</device>
<device label="Waveshare Relay" manufacturer="Waveshare">
<driver name="Waveshare Relay">indi_wavesharemodbus_relay</driver>
<version>1.0</version>
</device>
</devGroup>
<devGroup group="Domes">
<device label="ScopeDome Dome" manufacturer="ScopeDome">
Expand Down
10 changes: 10 additions & 0 deletions drivers/auxiliary/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,13 @@ SET(dragonlight_SRC
add_executable(indi_dragon_light ${dragonlight_SRC})
target_link_libraries(indi_dragon_light indidriver ${HTTPLIB_LIBRARY})
install(TARGETS indi_dragon_light RUNTIME DESTINATION bin)

# ########## Waveshare Relay ###############
SET(wavesharerelay_SRC
waveshare_modbus_relay.cpp
../../libs/modbus/nanomodbus.c
)

add_executable(indi_wavesharemodbus_relay ${wavesharerelay_SRC})
target_link_libraries(indi_wavesharemodbus_relay indidriver)
install(TARGETS indi_wavesharemodbus_relay RUNTIME DESTINATION bin)
208 changes: 208 additions & 0 deletions drivers/auxiliary/waveshare_modbus_relay.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
Waveshare ModBUS POE Relay
Copyright (C) 2024 Jasem Mutlaq
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "waveshare_modbus_relay.h"
#include "../../libs/modbus/platform.h"

#include <memory>
#include <unistd.h>
#include <connectionplugins/connectiontcp.h>

static std::unique_ptr<WaveshareRelay> sesto(new WaveshareRelay());

WaveshareRelay::WaveshareRelay() : RelayInterface(this)
{
setVersion(1, 0);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////////////////////////////
bool WaveshareRelay::initProperties()
{
INDI::DefaultDevice::initProperties();

INDI::RelayInterface::initProperties(MAIN_CONTROL_TAB, 8);

setDriverInterface(AUX_INTERFACE | RELAY_INTERFACE);

addAuxControls();

setDefaultPollingPeriod(2000);
tcpConnection = new Connection::TCP(this);
tcpConnection->setDefaultHost("192.168.1.1");
tcpConnection->setDefaultPort(502);
tcpConnection->registerHandshake([&]()
{
return Handshake();
});

registerConnection(tcpConnection);

return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////////////////////////////
bool WaveshareRelay::updateProperties()
{
INDI::DefaultDevice::updateProperties();
INDI::RelayInterface::updateProperties();

if (isConnected())
{
SetTimer(getPollingPeriod());
}
else
{
}

return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////////////////////////////
bool WaveshareRelay::Handshake()
{
PortFD = tcpConnection->getPortFD();

nmbs_platform_conf platform_conf;
platform_conf.transport = NMBS_TRANSPORT_TCP;
platform_conf.read = read_fd_linux;
platform_conf.write = write_fd_linux;
platform_conf.arg = &PortFD;

// Create the modbus client
nmbs_error err = nmbs_client_create(&nmbs, &platform_conf);
if (err != NMBS_ERROR_NONE)
{
LOGF_ERROR("Error creating modbus client: %d", err);
if (!nmbs_error_is_exception(err))
return false;
}

// Set only the response timeout. Byte timeout will be handled by the TCP connection
nmbs_set_read_timeout(&nmbs, 1000);

INDI::RelayInterface::Status status;
return QueryRelay(0, status);
}


////////////////////////////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////////////////////////////
const char *WaveshareRelay::getDefaultName()
{
return "Waveshare Relay";
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////////////////////////////
bool WaveshareRelay::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
{
// Check Relay Properties
if (INDI::RelayInterface::processText(dev, name, texts, names, n))
return true;

return INDI::DefaultDevice::ISNewText(dev, name, texts, names, n);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////////////////////////////
bool WaveshareRelay::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
{
if (INDI::RelayInterface::processSwitch(dev, name, states, names, n))
return true;

return INDI::DefaultDevice::ISNewSwitch(dev, name, states, names, n);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////////////////////////////
bool WaveshareRelay::saveConfigItems(FILE * fp)
{
INDI::RelayInterface::saveConfigItems(fp);
return INDI::DefaultDevice::saveConfigItems(fp);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////////////////////////////
void WaveshareRelay::TimerHit()
{
if (!isConnected())
return;

for (int i = 0; i < 8; i++)
{
INDI::RelayInterface::Status status;
if (QueryRelay(i, status))
{
RelaysSP[i].reset();
RelaysSP[i][status].setState(ISS_ON);
RelaysSP[i].setState(IPS_OK);
RelaysSP[i].apply();
}
}

SetTimer(getCurrentPollingPeriod());
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////////////////////////////
bool WaveshareRelay::QueryRelay(uint32_t index, Status &status)
{
nmbs_bitfield coils = {0};
auto err = nmbs_read_coils(&nmbs, 0, 8, coils);
if (err != NMBS_ERROR_NONE)
{
LOGF_ERROR("Error reading coils at address 0: %s", nmbs_strerror(err));
return false;
}
else
{
status = (nmbs_bitfield_read(coils, index) == 0) ? Closed : Opened;
return true;
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
///
////////////////////////////////////////////////////////////////////////////////////////////////////////
bool WaveshareRelay::CommandRelay(uint32_t index, Command command)
{
uint16_t value = 0;
if (command == Open)
value = 0xFF00;
else if (command == Flip)
value = 0x5500;

auto err = nmbs_write_single_coil(&nmbs, index, value);
if (err != NMBS_ERROR_NONE)
{
LOGF_ERROR("Error reading coils at address 0: %s", nmbs_strerror(err));
return false;
}

return true;
}
62 changes: 62 additions & 0 deletions drivers/auxiliary/waveshare_modbus_relay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
Waveshare ModBUS POE Relay
Copyright (C) 2024 Jasem Mutlaq
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#pragma once

#include "indirelayinterface.h"
#include "defaultdevice.h"
#include "../../libs/modbus/nanomodbus.h"

class WaveshareRelay : public INDI::DefaultDevice, public INDI::RelayInterface
{
public:
WaveshareRelay();
virtual ~WaveshareRelay() override = default;

const char *getDefaultName() override;
virtual bool initProperties() override;
virtual bool updateProperties() override;

virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override;
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override;

protected:
bool Handshake();

// Relay

/**
* \brief Query single relay status
* \param index Relay index
* \param status Store relay status in this variable.
* \return True if operation is successful, false otherwise
*/
virtual bool QueryRelay(uint32_t index, Status &status);

/**
* \brief Send command to relay
* \return True if operation is successful, false otherwise
*/
virtual bool CommandRelay(uint32_t index, Command command);

virtual void TimerHit() override;
virtual bool saveConfigItems(FILE *fp) override;

private:
Connection::TCP *tcpConnection {nullptr};
int PortFD{-1};
nmbs_t nmbs;
};
4 changes: 2 additions & 2 deletions libs/indibase/defaultdevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -838,13 +838,13 @@ bool DefaultDevice::updateProperties()
// The situation occurs in the case of drivers where the class that inherits from `DefaultDevice`
// will call the `setDriverInterface` function in the constructor

uint16_t DefaultDevice::getDriverInterface() const
uint32_t DefaultDevice::getDriverInterface() const
{
D_PTR(const DefaultDevice);
return atoi(d->DriverInfoTP[3 /* DRIVER_INTERFACE */].getText());
}

void DefaultDevice::setDriverInterface(uint16_t value)
void DefaultDevice::setDriverInterface(uint32_t value)
{
D_PTR(DefaultDevice);
d->DriverInfoTP[3 /* DRIVER_INTERFACE */].setText(std::to_string(value));
Expand Down
4 changes: 2 additions & 2 deletions libs/indibase/defaultdevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ class DefaultDevice : public ParentDevice
/**
* @return getInterface Return the interface declared by the driver.
*/
uint16_t getDriverInterface() const;
uint32_t getDriverInterface() const;

/**
* @brief setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE.
Expand All @@ -318,7 +318,7 @@ class DefaultDevice : public ParentDevice
* @warning This only updates the internal driver interface property and does not send it to the
* client. To synchronize the client, use syncDriverInfo function.
*/
void setDriverInterface(uint16_t value);
void setDriverInterface(uint32_t value);

public:
/** @brief Add a device to the watch list.
Expand Down
12 changes: 6 additions & 6 deletions libs/indibase/indirelayinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ void RelayInterface::initProperties(const char *groupName, uint8_t relays)
// Initialize labels
for (auto i = 0; i < relays; i++)
{
auto name = "RELAY_" + std::to_string(i);
auto label = "Relay #" + std::to_string(i);
auto name = "RELAY_" + std::to_string(i + 1);
auto label = "Relay #" + std::to_string(i + 1);

INDI::WidgetText oneLabel;
oneLabel.fill(name, label, label);
Expand All @@ -63,10 +63,10 @@ void RelayInterface::initProperties(const char *groupName, uint8_t relays)

RelaysSP.reserve(relays);
// Initialize switches, use labels if loaded.
for (auto i = 0; i < relays; i++)
for (size_t i = 0; i < relays; i++)
{
auto name = "RELAY_" + std::to_string(i);
auto label = "Relay #" + std::to_string(i);
auto name = "RELAY_" + std::to_string(i + 1);
auto label = "Relay #" + std::to_string(i + 1);

INDI::PropertySwitch oneRelay {3};
oneRelay[Open].fill("OPEN", "Open", ISS_OFF);
Expand Down Expand Up @@ -108,7 +108,7 @@ bool RelayInterface::processSwitch(const char *dev, const char *name, ISState st
{
if (dev && !strcmp(dev, m_defaultDevice->getDeviceName()))
{
for (auto i = 0; i < RelaysSP.size(); i++)
for (size_t i = 0; i < RelaysSP.size(); i++)
{
if (RelaysSP[i].isNameMatch(name))
{
Expand Down
Loading
Loading