mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
Add hyperion-usbasp led devices
Remove all WS281x direct UART code (does not work reliable) Former-commit-id: cd8103058d4ce0cd3280c7a2c5370397a14acf5c
This commit is contained in:
parent
dd0a18642b
commit
e22c720e68
@ -28,8 +28,7 @@ SET(Leddevice_HEADERS
|
|||||||
${CURRENT_SOURCE_DIR}/LedDevicePiBlaster.h
|
${CURRENT_SOURCE_DIR}/LedDevicePiBlaster.h
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceSedu.h
|
${CURRENT_SOURCE_DIR}/LedDeviceSedu.h
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceTest.h
|
${CURRENT_SOURCE_DIR}/LedDeviceTest.h
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceWs2812b.h
|
${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.h
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceWs2811.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SET(Leddevice_SOURCES
|
SET(Leddevice_SOURCES
|
||||||
@ -44,8 +43,7 @@ SET(Leddevice_SOURCES
|
|||||||
${CURRENT_SOURCE_DIR}/LedDevicePiBlaster.cpp
|
${CURRENT_SOURCE_DIR}/LedDevicePiBlaster.cpp
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceSedu.cpp
|
${CURRENT_SOURCE_DIR}/LedDeviceSedu.cpp
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceTest.cpp
|
${CURRENT_SOURCE_DIR}/LedDeviceTest.cpp
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceWs2811.cpp
|
${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.cpp
|
||||||
${CURRENT_SOURCE_DIR}/LedDeviceWs2812b.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if(ENABLE_SPIDEV)
|
if(ENABLE_SPIDEV)
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
#include "LedDevicePiBlaster.h"
|
#include "LedDevicePiBlaster.h"
|
||||||
#include "LedDeviceSedu.h"
|
#include "LedDeviceSedu.h"
|
||||||
#include "LedDeviceTest.h"
|
#include "LedDeviceTest.h"
|
||||||
#include "LedDeviceWs2811.h"
|
#include "LedDeviceHyperionUsbasp.h"
|
||||||
#include "LedDeviceWs2812b.h"
|
|
||||||
|
|
||||||
LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig)
|
LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig)
|
||||||
{
|
{
|
||||||
@ -87,23 +86,6 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig)
|
|||||||
device = deviceWs2801;
|
device = deviceWs2801;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// else if (type == "ws2811")
|
|
||||||
// {
|
|
||||||
// const std::string output = deviceConfig["output"].asString();
|
|
||||||
// const std::string outputSpeed = deviceConfig["output"].asString();
|
|
||||||
// const std::string timingOption = deviceConfig["timingOption"].asString();
|
|
||||||
|
|
||||||
// ws2811::SpeedMode speedMode = (outputSpeed == "high")? ws2811::highspeed : ws2811::lowspeed;
|
|
||||||
// if (outputSpeed != "high" && outputSpeed != "low")
|
|
||||||
// {
|
|
||||||
// std::cerr << "Incorrect speed-mode selected for WS2811: " << outputSpeed << " != {'high', 'low'}" << std::endl;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// LedDeviceWs2811 * deviceWs2811 = new LedDeviceWs2811(output, ws2811::fromString(timingOption, ws2811::option_2855), speedMode);
|
|
||||||
// deviceWs2811->open();
|
|
||||||
|
|
||||||
// device = deviceWs2811;
|
|
||||||
// }
|
|
||||||
else if (type == "lightpack")
|
else if (type == "lightpack")
|
||||||
{
|
{
|
||||||
const std::string output = deviceConfig.get("output", "").asString();
|
const std::string output = deviceConfig.get("output", "").asString();
|
||||||
@ -147,18 +129,23 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig)
|
|||||||
|
|
||||||
device = deviceSedu;
|
device = deviceSedu;
|
||||||
}
|
}
|
||||||
|
else if (type == "hyperion-usbasp-ws2801")
|
||||||
|
{
|
||||||
|
LedDeviceHyperionUsbasp * deviceHyperionUsbasp = new LedDeviceHyperionUsbasp(LedDeviceHyperionUsbasp::CMD_WRITE_WS2801);
|
||||||
|
deviceHyperionUsbasp->open();
|
||||||
|
device = deviceHyperionUsbasp;
|
||||||
|
}
|
||||||
|
else if (type == "hyperion-usbasp-ws2812")
|
||||||
|
{
|
||||||
|
LedDeviceHyperionUsbasp * deviceHyperionUsbasp = new LedDeviceHyperionUsbasp(LedDeviceHyperionUsbasp::CMD_WRITE_WS2812);
|
||||||
|
deviceHyperionUsbasp->open();
|
||||||
|
device = deviceHyperionUsbasp;
|
||||||
|
}
|
||||||
else if (type == "test")
|
else if (type == "test")
|
||||||
{
|
{
|
||||||
const std::string output = deviceConfig["output"].asString();
|
const std::string output = deviceConfig["output"].asString();
|
||||||
device = new LedDeviceTest(output);
|
device = new LedDeviceTest(output);
|
||||||
}
|
}
|
||||||
else if (type == "ws2812b")
|
|
||||||
{
|
|
||||||
LedDeviceWs2812b * deviceWs2812b = new LedDeviceWs2812b();
|
|
||||||
deviceWs2812b->open();
|
|
||||||
|
|
||||||
device = deviceWs2812b;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::cout << "Unable to create device " << type << std::endl;
|
std::cout << "Unable to create device " << type << std::endl;
|
||||||
|
205
libsrc/leddevice/LedDeviceHyperionUsbasp.cpp
Normal file
205
libsrc/leddevice/LedDeviceHyperionUsbasp.cpp
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// stl includes
|
||||||
|
#include <exception>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// Local Hyperion includes
|
||||||
|
#include "LedDeviceHyperionUsbasp.h"
|
||||||
|
|
||||||
|
// Static constants which define the Hyperion Usbasp device
|
||||||
|
uint16_t LedDeviceHyperionUsbasp::_usbVendorId = 0x16c0;
|
||||||
|
uint16_t LedDeviceHyperionUsbasp::_usbProductId = 0x05dc;
|
||||||
|
std::string LedDeviceHyperionUsbasp::_usbProductDescription = "Hyperion led controller";
|
||||||
|
|
||||||
|
|
||||||
|
LedDeviceHyperionUsbasp::LedDeviceHyperionUsbasp(uint8_t writeLedsCommand) :
|
||||||
|
LedDevice(),
|
||||||
|
_writeLedsCommand(writeLedsCommand),
|
||||||
|
_libusbContext(nullptr),
|
||||||
|
_deviceHandle(nullptr),
|
||||||
|
_ledCount(256)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LedDeviceHyperionUsbasp::~LedDeviceHyperionUsbasp()
|
||||||
|
{
|
||||||
|
if (_deviceHandle != nullptr)
|
||||||
|
{
|
||||||
|
libusb_release_interface(_deviceHandle, 0);
|
||||||
|
libusb_attach_kernel_driver(_deviceHandle, 0);
|
||||||
|
libusb_close(_deviceHandle);
|
||||||
|
|
||||||
|
_deviceHandle = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_libusbContext != nullptr)
|
||||||
|
{
|
||||||
|
libusb_exit(_libusbContext);
|
||||||
|
_libusbContext = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int LedDeviceHyperionUsbasp::open()
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
|
||||||
|
// initialize the usb context
|
||||||
|
if ((error = libusb_init(&_libusbContext)) != LIBUSB_SUCCESS)
|
||||||
|
{
|
||||||
|
std::cerr << "Error while initializing USB context(" << error << "): " << libusb_error_name(error) << std::endl;
|
||||||
|
_libusbContext = nullptr;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
//libusb_set_debug(_libusbContext, 3);
|
||||||
|
std::cout << "USB context initialized" << std::endl;
|
||||||
|
|
||||||
|
// retrieve the list of usb devices
|
||||||
|
libusb_device ** deviceList;
|
||||||
|
ssize_t deviceCount = libusb_get_device_list(_libusbContext, &deviceList);
|
||||||
|
|
||||||
|
// iterate the list of devices
|
||||||
|
for (ssize_t i = 0 ; i < deviceCount; ++i)
|
||||||
|
{
|
||||||
|
// try to open and initialize the device
|
||||||
|
error = testAndOpen(deviceList[i]);
|
||||||
|
|
||||||
|
if (error == 0)
|
||||||
|
{
|
||||||
|
// a device was sucessfully opened. break from list
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// free the device list
|
||||||
|
libusb_free_device_list(deviceList, 1);
|
||||||
|
|
||||||
|
if (_deviceHandle == nullptr)
|
||||||
|
{
|
||||||
|
std::cerr << "No " << _usbProductDescription << " has been found" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _deviceHandle == nullptr ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LedDeviceHyperionUsbasp::testAndOpen(libusb_device * device)
|
||||||
|
{
|
||||||
|
libusb_device_descriptor deviceDescriptor;
|
||||||
|
int error = libusb_get_device_descriptor(device, &deviceDescriptor);
|
||||||
|
if (error != LIBUSB_SUCCESS)
|
||||||
|
{
|
||||||
|
std::cerr << "Error while retrieving device descriptor(" << error << "): " << libusb_error_name(error) << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceDescriptor.idVendor == _usbVendorId &&
|
||||||
|
deviceDescriptor.idProduct == _usbProductId &&
|
||||||
|
deviceDescriptor.iProduct != 0 &&
|
||||||
|
getString(device, deviceDescriptor.iProduct) == _usbProductDescription)
|
||||||
|
{
|
||||||
|
// get the hardware address
|
||||||
|
int busNumber = libusb_get_bus_number(device);
|
||||||
|
int addressNumber = libusb_get_device_address(device);
|
||||||
|
|
||||||
|
std::cout << _usbProductDescription << " found: bus=" << busNumber << " address=" << addressNumber << std::endl;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_deviceHandle = openDevice(device);
|
||||||
|
std::cout << _usbProductDescription << " successfully opened" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch(int e)
|
||||||
|
{
|
||||||
|
_deviceHandle = nullptr;
|
||||||
|
std::cerr << "Unable to open " << _usbProductDescription << ". Searching for other device(" << e << "): " << libusb_error_name(e) << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LedDeviceHyperionUsbasp::write(const std::vector<ColorRgb> &ledValues)
|
||||||
|
{
|
||||||
|
_ledCount = ledValues.size();
|
||||||
|
|
||||||
|
int nbytes = libusb_control_transfer(
|
||||||
|
_deviceHandle, // device handle
|
||||||
|
LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT, // request type
|
||||||
|
_writeLedsCommand, // request
|
||||||
|
0, // value
|
||||||
|
0, // index
|
||||||
|
(uint8_t *) ledValues.data(), // data
|
||||||
|
(3*_ledCount) & 0xffff, // length
|
||||||
|
5000); // timeout
|
||||||
|
|
||||||
|
// Disabling interupts for a little while on the device results in a PIPE error. All seems to keep functioning though...
|
||||||
|
if(nbytes < 0 && nbytes != LIBUSB_ERROR_PIPE)
|
||||||
|
{
|
||||||
|
std::cerr << "Error while writing data to " << _usbProductDescription << " (" << libusb_error_name(nbytes) << ")" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LedDeviceHyperionUsbasp::switchOff()
|
||||||
|
{
|
||||||
|
std::vector<ColorRgb> ledValues(_ledCount, ColorRgb::BLACK);
|
||||||
|
return write(ledValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_device_handle * LedDeviceHyperionUsbasp::openDevice(libusb_device *device)
|
||||||
|
{
|
||||||
|
libusb_device_handle * handle = nullptr;
|
||||||
|
|
||||||
|
int error = libusb_open(device, &handle);
|
||||||
|
if (error != LIBUSB_SUCCESS)
|
||||||
|
{
|
||||||
|
std::cerr << "unable to open device(" << error << "): " << libusb_error_name(error) << std::endl;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// detach kernel driver if it is active
|
||||||
|
if (libusb_kernel_driver_active(handle, 0) == 1)
|
||||||
|
{
|
||||||
|
error = libusb_detach_kernel_driver(handle, 0);
|
||||||
|
if (error != LIBUSB_SUCCESS)
|
||||||
|
{
|
||||||
|
std::cerr << "unable to detach kernel driver(" << error << "): " << libusb_error_name(error) << std::endl;
|
||||||
|
libusb_close(handle);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error = libusb_claim_interface(handle, 0);
|
||||||
|
if (error != LIBUSB_SUCCESS)
|
||||||
|
{
|
||||||
|
std::cerr << "unable to claim interface(" << error << "): " << libusb_error_name(error) << std::endl;
|
||||||
|
libusb_attach_kernel_driver(handle, 0);
|
||||||
|
libusb_close(handle);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string LedDeviceHyperionUsbasp::getString(libusb_device * device, int stringDescriptorIndex)
|
||||||
|
{
|
||||||
|
libusb_device_handle * handle = nullptr;
|
||||||
|
|
||||||
|
int error = libusb_open(device, &handle);
|
||||||
|
if (error != LIBUSB_SUCCESS)
|
||||||
|
{
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[256];
|
||||||
|
error = libusb_get_string_descriptor_ascii(handle, stringDescriptorIndex, reinterpret_cast<unsigned char *>(buffer), sizeof(buffer));
|
||||||
|
if (error <= 0)
|
||||||
|
{
|
||||||
|
libusb_close(handle);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_close(handle);
|
||||||
|
return std::string(buffer, error);
|
||||||
|
}
|
88
libsrc/leddevice/LedDeviceHyperionUsbasp.h
Normal file
88
libsrc/leddevice/LedDeviceHyperionUsbasp.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// stl includes
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// libusb include
|
||||||
|
#include <libusb.h>
|
||||||
|
|
||||||
|
// Hyperion includes
|
||||||
|
#include <leddevice/LedDevice.h>
|
||||||
|
|
||||||
|
///
|
||||||
|
/// LedDevice implementation for a lightpack device (http://code.google.com/p/light-pack/)
|
||||||
|
///
|
||||||
|
class LedDeviceHyperionUsbasp : public LedDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Commands to the Device
|
||||||
|
enum Commands {
|
||||||
|
CMD_WRITE_WS2801 = 10,
|
||||||
|
CMD_WRITE_WS2812 = 11
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Constructs the LedDeviceLightpack
|
||||||
|
///
|
||||||
|
LedDeviceHyperionUsbasp(uint8_t writeLedsCommand);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Destructor of the LedDevice; closes the output device if it is open
|
||||||
|
///
|
||||||
|
virtual ~LedDeviceHyperionUsbasp();
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Opens and configures the output device
|
||||||
|
///
|
||||||
|
/// @return Zero on succes else negative
|
||||||
|
///
|
||||||
|
int open();
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Writes the RGB-Color values to the leds.
|
||||||
|
///
|
||||||
|
/// @param[in] ledValues The RGB-color per led
|
||||||
|
///
|
||||||
|
/// @return Zero on success else negative
|
||||||
|
///
|
||||||
|
virtual int write(const std::vector<ColorRgb>& ledValues);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Switch the leds off
|
||||||
|
///
|
||||||
|
/// @return Zero on success else negative
|
||||||
|
///
|
||||||
|
virtual int switchOff();
|
||||||
|
|
||||||
|
private:
|
||||||
|
///
|
||||||
|
/// Test if the device is a Hyperion Usbasp device
|
||||||
|
///
|
||||||
|
/// @return Zero on succes else negative
|
||||||
|
///
|
||||||
|
int testAndOpen(libusb_device * device);
|
||||||
|
|
||||||
|
static libusb_device_handle * openDevice(libusb_device * device);
|
||||||
|
|
||||||
|
static std::string getString(libusb_device * device, int stringDescriptorIndex);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// command to write the leds
|
||||||
|
const uint8_t _writeLedsCommand;
|
||||||
|
|
||||||
|
/// libusb context
|
||||||
|
libusb_context * _libusbContext;
|
||||||
|
|
||||||
|
/// libusb device handle
|
||||||
|
libusb_device_handle * _deviceHandle;
|
||||||
|
|
||||||
|
/// Number of leds
|
||||||
|
int _ledCount;
|
||||||
|
|
||||||
|
/// Usb device identifiers
|
||||||
|
static uint16_t _usbVendorId;
|
||||||
|
static uint16_t _usbProductId;
|
||||||
|
static std::string _usbProductDescription;
|
||||||
|
};
|
@ -1,182 +0,0 @@
|
|||||||
|
|
||||||
// Local hyperion includes
|
|
||||||
#include "LedDeviceWs2811.h"
|
|
||||||
|
|
||||||
|
|
||||||
ws2811::SignalTiming ws2811::fromString(const std::string& signalTiming, const SignalTiming defaultValue)
|
|
||||||
{
|
|
||||||
SignalTiming result = defaultValue;
|
|
||||||
if (signalTiming == "3755" || signalTiming == "option_3755")
|
|
||||||
{
|
|
||||||
result = option_3755;
|
|
||||||
}
|
|
||||||
else if (signalTiming == "3773" || signalTiming == "option_3773")
|
|
||||||
{
|
|
||||||
result = option_3773;
|
|
||||||
}
|
|
||||||
else if (signalTiming == "2855" || signalTiming == "option_2855")
|
|
||||||
{
|
|
||||||
result = option_2855;
|
|
||||||
}
|
|
||||||
else if (signalTiming == "2882" || signalTiming == "option_2882")
|
|
||||||
{
|
|
||||||
result = option_2882;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned ws2811::getBaudrate(const SpeedMode speedMode)
|
|
||||||
{
|
|
||||||
switch (speedMode)
|
|
||||||
{
|
|
||||||
case highspeed:
|
|
||||||
// Bit length: 125ns
|
|
||||||
return 8000000;
|
|
||||||
case lowspeed:
|
|
||||||
// Bit length: 250ns
|
|
||||||
return 4000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
inline unsigned ws2811::getLength(const SignalTiming timing, const TimeOption option)
|
|
||||||
{
|
|
||||||
switch (timing)
|
|
||||||
{
|
|
||||||
case option_3755:
|
|
||||||
// Reference: http://www.mikrocontroller.net/attachment/180459/WS2812B_preliminary.pdf
|
|
||||||
// Unit length: 125ns
|
|
||||||
switch (option)
|
|
||||||
{
|
|
||||||
case T0H:
|
|
||||||
return 3; // 400ns +-150ns
|
|
||||||
case T0L:
|
|
||||||
return 7; // 850ns +-150ns
|
|
||||||
case T1H:
|
|
||||||
return 7; // 800ns +-150ns
|
|
||||||
case T1L:
|
|
||||||
return 3; // 450ns +-150ns
|
|
||||||
}
|
|
||||||
case option_3773:
|
|
||||||
// Reference: www.adafruit.com/datasheets/WS2812.pdf
|
|
||||||
// Unit length: 125ns
|
|
||||||
switch (option)
|
|
||||||
{
|
|
||||||
case T0H:
|
|
||||||
return 3; // 350ns +-150ns
|
|
||||||
case T0L:
|
|
||||||
return 7; // 800ns +-150ns
|
|
||||||
case T1H:
|
|
||||||
return 7; // 700ns +-150ns
|
|
||||||
case T1L:
|
|
||||||
return 3; // 600ns +-150ns
|
|
||||||
}
|
|
||||||
case option_2855:
|
|
||||||
// Reference: www.adafruit.com/datasheets/WS2811.pdf
|
|
||||||
// Unit length: 250ns
|
|
||||||
switch (option)
|
|
||||||
{
|
|
||||||
case T0H:
|
|
||||||
return 2; // 500ns +-150ns
|
|
||||||
case T0L:
|
|
||||||
return 8; // 2000ns +-150ns
|
|
||||||
case T1H:
|
|
||||||
return 5; // 1200ns +-150ns
|
|
||||||
case T1L:
|
|
||||||
return 5; // 1300ns +-150ns
|
|
||||||
}
|
|
||||||
case option_2882:
|
|
||||||
// Reference: www.szparkson.net/download/WS2811.pdf
|
|
||||||
// Unit length: 250ns
|
|
||||||
switch (option)
|
|
||||||
{
|
|
||||||
case T0H:
|
|
||||||
return 2; // 500ns +-150ns
|
|
||||||
case T0L:
|
|
||||||
return 8; // 2000ns +-150ns
|
|
||||||
case T1H:
|
|
||||||
return 8; // 2000ns +-150ns
|
|
||||||
case T1L:
|
|
||||||
return 2; // 500ns +-150ns
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
std::cerr << "Unknown signal timing for ws2811: " << timing << std::endl;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ws2811::bitToSignal(unsigned lenHigh)
|
|
||||||
{
|
|
||||||
// Sanity check on the length of the 'high' signal
|
|
||||||
assert(0 < lenHigh && lenHigh < 10);
|
|
||||||
|
|
||||||
uint8_t result = 0x00;
|
|
||||||
for (unsigned i=1; i<lenHigh; ++i)
|
|
||||||
{
|
|
||||||
result |= (1 << (8-i));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ws2811::ByteSignal ws2811::translate(SignalTiming ledOption, uint8_t byte)
|
|
||||||
{
|
|
||||||
ByteSignal result;
|
|
||||||
result.bit_1 = bitToSignal(getLength(ledOption, (byte & 0x80)?T1H:T0H));
|
|
||||||
result.bit_2 = bitToSignal(getLength(ledOption, (byte & 0x40)?T1H:T0H));
|
|
||||||
result.bit_3 = bitToSignal(getLength(ledOption, (byte & 0x20)?T1H:T0H));
|
|
||||||
result.bit_4 = bitToSignal(getLength(ledOption, (byte & 0x10)?T1H:T0H));
|
|
||||||
result.bit_5 = bitToSignal(getLength(ledOption, (byte & 0x08)?T1H:T0H));
|
|
||||||
result.bit_6 = bitToSignal(getLength(ledOption, (byte & 0x04)?T1H:T0H));
|
|
||||||
result.bit_7 = bitToSignal(getLength(ledOption, (byte & 0x02)?T1H:T0H));
|
|
||||||
result.bit_8 = bitToSignal(getLength(ledOption, (byte & 0x01)?T1H:T0H));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
LedDeviceWs2811::LedDeviceWs2811(
|
|
||||||
const std::string & outputDevice,
|
|
||||||
const ws2811::SignalTiming signalTiming,
|
|
||||||
const ws2811::SpeedMode speedMode) :
|
|
||||||
LedRs232Device(outputDevice, ws2811::getBaudrate(speedMode))
|
|
||||||
{
|
|
||||||
fillEncodeTable(signalTiming);
|
|
||||||
}
|
|
||||||
|
|
||||||
int LedDeviceWs2811::write(const std::vector<ColorRgb> & ledValues)
|
|
||||||
{
|
|
||||||
if (_ledBuffer.size() != ledValues.size() * 3)
|
|
||||||
{
|
|
||||||
_ledBuffer.resize(ledValues.size() * 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto bufIt = _ledBuffer.begin();
|
|
||||||
for (const ColorRgb & color : ledValues)
|
|
||||||
{
|
|
||||||
*bufIt = _byteToSignalTable[color.red ];
|
|
||||||
++bufIt;
|
|
||||||
*bufIt = _byteToSignalTable[color.green];
|
|
||||||
++bufIt;
|
|
||||||
*bufIt = _byteToSignalTable[color.blue ];
|
|
||||||
++bufIt;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeBytes(_ledBuffer.size() * 3, reinterpret_cast<uint8_t *>(_ledBuffer.data()));
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LedDeviceWs2811::switchOff()
|
|
||||||
{
|
|
||||||
write(std::vector<ColorRgb>(_ledBuffer.size()/3, ColorRgb::BLACK));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LedDeviceWs2811::fillEncodeTable(const ws2811::SignalTiming ledOption)
|
|
||||||
{
|
|
||||||
_byteToSignalTable.resize(256);
|
|
||||||
for (unsigned byteValue=0; byteValue<256; ++byteValue)
|
|
||||||
{
|
|
||||||
const uint8_t byteVal = uint8_t(byteValue);
|
|
||||||
_byteToSignalTable[byteValue] = ws2811::translate(ledOption, byteVal);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
// STL includes
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
// Local hyperion includes
|
|
||||||
#include "LedRs232Device.h"
|
|
||||||
|
|
||||||
namespace ws2811
|
|
||||||
{
|
|
||||||
///
|
|
||||||
/// Enumaration of known signal timings
|
|
||||||
///
|
|
||||||
enum SignalTiming
|
|
||||||
{
|
|
||||||
option_3755,
|
|
||||||
option_3773,
|
|
||||||
option_2855,
|
|
||||||
option_2882,
|
|
||||||
not_a_signaltiming
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Enumaration of the possible speeds on which the ws2811 can operate.
|
|
||||||
///
|
|
||||||
enum SpeedMode
|
|
||||||
{
|
|
||||||
lowspeed,
|
|
||||||
highspeed
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Enumeration of the signal 'parts' (T 0 high, T 1 high, T 0 low, T 1 low).
|
|
||||||
///
|
|
||||||
enum TimeOption
|
|
||||||
{
|
|
||||||
T0H,
|
|
||||||
T1H,
|
|
||||||
T0L,
|
|
||||||
T1L
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Structure holding the signal for a signle byte
|
|
||||||
///
|
|
||||||
struct ByteSignal
|
|
||||||
{
|
|
||||||
uint8_t bit_1;
|
|
||||||
uint8_t bit_2;
|
|
||||||
uint8_t bit_3;
|
|
||||||
uint8_t bit_4;
|
|
||||||
uint8_t bit_5;
|
|
||||||
uint8_t bit_6;
|
|
||||||
uint8_t bit_7;
|
|
||||||
uint8_t bit_8;
|
|
||||||
};
|
|
||||||
// Make sure the structure is exatly the length we require
|
|
||||||
static_assert(sizeof(ByteSignal) == 8, "Incorrect sizeof ByteSignal (expected 8)");
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Translates a string to a signal timing
|
|
||||||
///
|
|
||||||
/// @param signalTiming The string specifying the signal timing
|
|
||||||
/// @param defaultValue The default value (used if the string does not match any known timing)
|
|
||||||
///
|
|
||||||
/// @return The SignalTiming (or not_a_signaltiming if it did not match)
|
|
||||||
///
|
|
||||||
SignalTiming fromString(const std::string& signalTiming, const SignalTiming defaultValue);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Returns the required baudrate for a specific signal-timing
|
|
||||||
///
|
|
||||||
/// @param SpeedMode The WS2811/WS2812 speed mode (WS2812b only has highspeed)
|
|
||||||
///
|
|
||||||
/// @return The required baudrate for the signal timing
|
|
||||||
///
|
|
||||||
unsigned getBaudrate(const SpeedMode speedMode);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The number of 'signal units' (bits) For the subpart of a specific timing scheme
|
|
||||||
///
|
|
||||||
/// @param timing The controller option
|
|
||||||
/// @param option The signal part
|
|
||||||
///
|
|
||||||
unsigned getLength(const SignalTiming timing, const TimeOption option);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Constructs a 'bit' based signal with defined 'high' length (and implicite defined 'low'
|
|
||||||
/// length. The signal is based on a 10bits bytes (incl. high startbit and low stopbit). The
|
|
||||||
/// total length of the high is given as parameter:<br>
|
|
||||||
/// lenHigh=7 => |-------|___| => 1 1111 1100 0 => 252 (start and stop bit are implicite)
|
|
||||||
///
|
|
||||||
/// @param lenHigh The total length of the 'high' length (incl start-bit)
|
|
||||||
/// @return The byte representing the high-low signal
|
|
||||||
///
|
|
||||||
uint8_t bitToSignal(unsigned lenHigh);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Translate a byte into signal levels for a specific WS2811 option
|
|
||||||
///
|
|
||||||
/// @param ledOption The WS2811 configuration
|
|
||||||
/// @param byte The byte to translate
|
|
||||||
///
|
|
||||||
/// @return The signal for the given byte (one byte per bit)
|
|
||||||
///
|
|
||||||
ByteSignal translate(SignalTiming ledOption, uint8_t byte);
|
|
||||||
}
|
|
||||||
|
|
||||||
class LedDeviceWs2811 : public LedRs232Device
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
///
|
|
||||||
/// Constructs the LedDevice with Ws2811 attached via a serial port
|
|
||||||
///
|
|
||||||
/// @param outputDevice The name of the output device (eg '/dev/ttyS0')
|
|
||||||
/// @param signalTiming The timing scheme used by the Ws2811 chip
|
|
||||||
/// @param speedMode The speed modus of the Ws2811 chip
|
|
||||||
///
|
|
||||||
LedDeviceWs2811(const std::string& outputDevice, const ws2811::SignalTiming signalTiming, const ws2811::SpeedMode speedMode);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Writes the led color values to the led-device
|
|
||||||
///
|
|
||||||
/// @param ledValues The color-value per led
|
|
||||||
/// @return Zero on succes else negative
|
|
||||||
///
|
|
||||||
virtual int write(const std::vector<ColorRgb> & ledValues);
|
|
||||||
|
|
||||||
/// Switch the leds off
|
|
||||||
virtual int switchOff();
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Fill the byte encoding table (_byteToSignalTable) for the specific timing option
|
|
||||||
///
|
|
||||||
/// @param ledOption The timing option
|
|
||||||
///
|
|
||||||
void fillEncodeTable(const ws2811::SignalTiming ledOption);
|
|
||||||
|
|
||||||
/// Translation table of byte to signal///
|
|
||||||
std::vector<ws2811::ByteSignal> _byteToSignalTable;
|
|
||||||
|
|
||||||
/// The buffer containing the packed RGB values
|
|
||||||
std::vector<ws2811::ByteSignal> _ledBuffer;
|
|
||||||
};
|
|
@ -1,96 +0,0 @@
|
|||||||
|
|
||||||
// Linux includes
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
// Local Hyperion-Leddevice includes
|
|
||||||
#include "LedDeviceWs2812b.h"
|
|
||||||
|
|
||||||
LedDeviceWs2812b::LedDeviceWs2812b() :
|
|
||||||
LedRs232Device("/dev/ttyUSB0", 2000000)
|
|
||||||
{
|
|
||||||
// empty
|
|
||||||
}
|
|
||||||
|
|
||||||
int LedDeviceWs2812b::write(const std::vector<ColorRgb> & ledValues)
|
|
||||||
{
|
|
||||||
// Ensure the size of the led-buffer
|
|
||||||
if (_ledBuffer.size() != ledValues.size()*8)
|
|
||||||
{
|
|
||||||
_ledBuffer.resize(ledValues.size()*8, ~0x24);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate the channel of each color to a signal
|
|
||||||
uint8_t * signal_ptr = _ledBuffer.data();
|
|
||||||
for (const ColorRgb & color : ledValues)
|
|
||||||
{
|
|
||||||
signal_ptr = color2signal(color, signal_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int result = writeBytes(_ledBuffer.size(), _ledBuffer.data());
|
|
||||||
// Official latch time is 50us (lets give it 50us more)
|
|
||||||
usleep(100);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t * LedDeviceWs2812b::color2signal(const ColorRgb & color, uint8_t * signal)
|
|
||||||
{
|
|
||||||
*signal = bits2Signal(color.red & 0x80, color.red & 0x40, color.red & 0x20);
|
|
||||||
++signal;
|
|
||||||
*signal = bits2Signal(color.red & 0x10, color.red & 0x08, color.red & 0x04);
|
|
||||||
++signal;
|
|
||||||
*signal = bits2Signal(color.red & 0x02, color.green & 0x01, color.green & 0x80);
|
|
||||||
++signal;
|
|
||||||
*signal = bits2Signal(color.green & 0x40, color.green & 0x20, color.green & 0x10);
|
|
||||||
++signal;
|
|
||||||
*signal = bits2Signal(color.green & 0x08, color.green & 0x04, color.green & 0x02);
|
|
||||||
++signal;
|
|
||||||
*signal = bits2Signal(color.green & 0x01, color.blue & 0x80, color.blue & 0x40);
|
|
||||||
++signal;
|
|
||||||
*signal = bits2Signal(color.blue & 0x20, color.blue & 0x10, color.blue & 0x08);
|
|
||||||
++signal;
|
|
||||||
*signal = bits2Signal(color.blue & 0x04, color.blue & 0x02, color.blue & 0x01);
|
|
||||||
++signal;
|
|
||||||
|
|
||||||
return signal;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LedDeviceWs2812b::switchOff()
|
|
||||||
{
|
|
||||||
// Set all bytes in the signal buffer to zero
|
|
||||||
for (uint8_t & signal : _ledBuffer)
|
|
||||||
{
|
|
||||||
signal = ~0x24;
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeBytes(_ledBuffer.size(), _ledBuffer.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t LedDeviceWs2812b::bits2Signal(const bool bit_1, const bool bit_2, const bool bit_3) const
|
|
||||||
{
|
|
||||||
// See https://github.com/tvdzwan/hyperion/wiki/Ws2812b for the explanation of the given
|
|
||||||
// translations
|
|
||||||
|
|
||||||
// Bit index(default):1 2 3
|
|
||||||
// | | |
|
|
||||||
// default value (1) 00 100 10 (0)
|
|
||||||
//
|
|
||||||
// Reversed value (1) 01 001 00 (0)
|
|
||||||
// | | |
|
|
||||||
// Bit index (rev): 3 2 1
|
|
||||||
uint8_t result = 0x24;
|
|
||||||
|
|
||||||
if(bit_1)
|
|
||||||
{
|
|
||||||
result |= 0x01;
|
|
||||||
}
|
|
||||||
if (bit_2)
|
|
||||||
{
|
|
||||||
result |= 0x08;
|
|
||||||
}
|
|
||||||
if (bit_3)
|
|
||||||
{
|
|
||||||
result |= 0x40;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ~result;
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// Hyperion leddevice includes
|
|
||||||
#include "LedRs232Device.h"
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The LedDevice for controlling a string of WS2812B leds. These are controlled over the mini-UART
|
|
||||||
/// of the RPi (/dev/ttyAMA0).
|
|
||||||
///
|
|
||||||
class LedDeviceWs2812b : public LedRs232Device
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
///
|
|
||||||
/// Constructs the device (all required parameters are hardcoded)
|
|
||||||
///
|
|
||||||
LedDeviceWs2812b();
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Write the color data the the WS2812B led string
|
|
||||||
///
|
|
||||||
/// @param ledValues The color data
|
|
||||||
/// @return Zero on succes else negative
|
|
||||||
///
|
|
||||||
virtual int write(const std::vector<ColorRgb> & ledValues);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Write zero to all leds(that have been written by a previous write operation)
|
|
||||||
///
|
|
||||||
/// @return Zero on succes else negative
|
|
||||||
///
|
|
||||||
virtual int switchOff();
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Translate a color to the signal bits. The resulting bits are written to the given memory.
|
|
||||||
///
|
|
||||||
/// @param color The color to translate
|
|
||||||
/// @param signal The pointer at the beginning of the signal to write
|
|
||||||
/// @return The pointer at the end of the written signal
|
|
||||||
///
|
|
||||||
uint8_t * color2signal(const ColorRgb & color, uint8_t * signal);
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Translates three bits to a single byte
|
|
||||||
///
|
|
||||||
/// @param bit1 The value of the first bit (1=true, zero=false)
|
|
||||||
/// @param bit2 The value of the second bit (1=true, zero=false)
|
|
||||||
/// @param bit3 The value of the third bit (1=true, zero=false)
|
|
||||||
///
|
|
||||||
/// @return The output-byte for the given two bit
|
|
||||||
///
|
|
||||||
uint8_t bits2Signal(const bool bit1, const bool bit2, const bool bit3) const;
|
|
||||||
|
|
||||||
///
|
|
||||||
/// The output buffer for writing bytes to the output
|
|
||||||
///
|
|
||||||
std::vector<uint8_t> _ledBuffer;
|
|
||||||
};
|
|
@ -52,20 +52,3 @@ target_link_libraries(test_qregexp
|
|||||||
add_executable(test_qtscreenshot TestQtScreenshot.cpp)
|
add_executable(test_qtscreenshot TestQtScreenshot.cpp)
|
||||||
target_link_libraries(test_qtscreenshot
|
target_link_libraries(test_qtscreenshot
|
||||||
${QT_LIBRARIES})
|
${QT_LIBRARIES})
|
||||||
|
|
||||||
add_executable(determineWs2811Timing DetermineWs2811Timing.cpp)
|
|
||||||
|
|
||||||
add_executable(test_rs232highspeed
|
|
||||||
TestRs232HighSpeed.cpp
|
|
||||||
../libsrc/leddevice/LedRs232Device.cpp
|
|
||||||
../libsrc/leddevice/LedDeviceWs2812b.cpp)
|
|
||||||
target_link_libraries(test_rs232highspeed
|
|
||||||
serialport)
|
|
||||||
|
|
||||||
if(NOT APPLE AND UNIX)
|
|
||||||
include_directories(/usr/include)
|
|
||||||
add_executable(test_uartHighSpeed TestUartHighSpeed.cpp)
|
|
||||||
|
|
||||||
add_executable(test_nonUniformWs2812b TestNonUniformWs2812b.cpp)
|
|
||||||
add_executable(test_nonInvWs2812b TestNonInvWs2812b.cpp)
|
|
||||||
endif()
|
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
|
|
||||||
// STl includes
|
|
||||||
#include <iostream>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
bool requiredTiming(const int tHigh_ns, const int tLow_ns, const int error_ns, const int nrBits)
|
|
||||||
{
|
|
||||||
std::cout << "=== " << nrBits << " bits case ===== " << std::endl;
|
|
||||||
double bitLength_ns = (tHigh_ns + tLow_ns)/double(nrBits);
|
|
||||||
double baudrate_Hz = 1.0 / bitLength_ns * 1e9;
|
|
||||||
std::cout << "Required bit length: " << bitLength_ns << "ns => baudrate = " << baudrate_Hz << std::endl;
|
|
||||||
|
|
||||||
double highBitsExact = tHigh_ns/bitLength_ns;
|
|
||||||
int highBits = std::round(highBitsExact);
|
|
||||||
double lowBitsExact = tLow_ns/bitLength_ns;
|
|
||||||
int lowBits = std::round(lowBitsExact);
|
|
||||||
std::cout << "Bit division: high=" << highBits << "(" << highBitsExact << "); low=" << lowBits << "(" << lowBitsExact << ")" << std::endl;
|
|
||||||
|
|
||||||
double highBitsError = std::fabs(highBitsExact - highBits);
|
|
||||||
double lowBitsError = std::fabs(highBitsExact - highBits);
|
|
||||||
double highError_ns = highBitsError * bitLength_ns;
|
|
||||||
double lowError_ns = lowBitsError * bitLength_ns;
|
|
||||||
|
|
||||||
if (highError_ns > error_ns || lowError_ns > error_ns)
|
|
||||||
{
|
|
||||||
std::cerr << "Timing error outside specs: " << highError_ns << "; " << lowError_ns << " > " << error_ns << std::endl;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << "Timing within margins: " << highError_ns << "; " << lowError_ns << " < " << error_ns << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
// 10bits
|
|
||||||
requiredTiming(400, 850, 150, 10); // Zero
|
|
||||||
requiredTiming(800, 450, 150, 10); // One
|
|
||||||
|
|
||||||
// 6bits
|
|
||||||
requiredTiming(400, 850, 150, 6); // Zero
|
|
||||||
requiredTiming(800, 450, 150, 6); // One
|
|
||||||
|
|
||||||
// 5bits
|
|
||||||
requiredTiming(400, 850, 150, 5); // Zero
|
|
||||||
requiredTiming(800, 450, 150, 5); // One
|
|
||||||
|
|
||||||
requiredTiming(650, 600, 150, 5); // One
|
|
||||||
|
|
||||||
// 4bits
|
|
||||||
requiredTiming(400, 850, 150, 4); // Zero
|
|
||||||
requiredTiming(800, 450, 150, 4); // One
|
|
||||||
|
|
||||||
// 3bits
|
|
||||||
requiredTiming(400, 850, 150, 3); // Zero
|
|
||||||
requiredTiming(800, 450, 150, 3); // One
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,209 +0,0 @@
|
|||||||
|
|
||||||
// STL includes
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include <unistd.h> //Used for UART
|
|
||||||
#include <fcntl.h> //Used for UART
|
|
||||||
#include <termios.h> //Used for UART
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
|
|
||||||
std::vector<uint8_t> encode(const std::vector<uint8_t> & data);
|
|
||||||
void split(const uint8_t byte, uint8_t & out1, uint8_t & out2);
|
|
||||||
uint8_t encode(const bool bit1, const bool bit2, const bool bit3);
|
|
||||||
|
|
||||||
void print(uint8_t byte)
|
|
||||||
{
|
|
||||||
for (int i=0; i<8; ++i)
|
|
||||||
{
|
|
||||||
if (byte & (1 << i))
|
|
||||||
{
|
|
||||||
std::cout << '1';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << '0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void printClockSignal(const std::vector<uint8_t> & signal)
|
|
||||||
{
|
|
||||||
bool prevBit = true;
|
|
||||||
bool nextBit = true;
|
|
||||||
|
|
||||||
for (uint8_t byte : signal)
|
|
||||||
{
|
|
||||||
|
|
||||||
for (int i=-1; i<9; ++i)
|
|
||||||
{
|
|
||||||
if (i == -1) // Start bit
|
|
||||||
nextBit = false;
|
|
||||||
else if (i == 8) // Stop bit
|
|
||||||
nextBit = true;
|
|
||||||
else
|
|
||||||
nextBit = byte & (1 << i);
|
|
||||||
|
|
||||||
if (!prevBit && nextBit)
|
|
||||||
{
|
|
||||||
std::cout << ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextBit)
|
|
||||||
std::cout << '1';
|
|
||||||
else
|
|
||||||
std::cout << '0';
|
|
||||||
|
|
||||||
prevBit = nextBit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
const std::vector<uint8_t> data(9, 0x00);
|
|
||||||
std::vector<uint8_t> encData = encode(data);
|
|
||||||
|
|
||||||
for (uint8_t encByte : encData)
|
|
||||||
{
|
|
||||||
std::cout << "0 ";
|
|
||||||
print(encByte);
|
|
||||||
std::cout << " 1";
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
printClockSignal(encData);
|
|
||||||
std::cout << std::endl;
|
|
||||||
|
|
||||||
//OPEN THE UART
|
|
||||||
// int uart0_filestream = open("/dev/ttyAMA0", O_WRONLY | O_NOCTTY | O_NDELAY);
|
|
||||||
int uart0_filestream = open("/dev/ttyUSB0", O_WRONLY | O_NOCTTY | O_NDELAY);
|
|
||||||
if (uart0_filestream == -1)
|
|
||||||
{
|
|
||||||
//ERROR - CAN'T OPEN SERIAL PORT
|
|
||||||
printf("Error - Unable to open UART. Ensure it is not in use by another application\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the port
|
|
||||||
struct termios options;
|
|
||||||
tcgetattr(uart0_filestream, &options);
|
|
||||||
options.c_cflag = B2500000 | CS8 | CLOCAL;
|
|
||||||
options.c_iflag = IGNPAR;
|
|
||||||
options.c_oflag = 0;
|
|
||||||
options.c_lflag = 0;
|
|
||||||
|
|
||||||
tcflush(uart0_filestream, TCIFLUSH);
|
|
||||||
tcsetattr(uart0_filestream, TCSANOW, &options);
|
|
||||||
|
|
||||||
getchar();
|
|
||||||
|
|
||||||
const int breakLength_ms = 1;
|
|
||||||
|
|
||||||
encData = std::vector<uint8_t>(128, 0x00);
|
|
||||||
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
|
|
||||||
tcsendbreak(uart0_filestream, breakLength_ms);
|
|
||||||
|
|
||||||
//tcdrain(uart0_filestream);
|
|
||||||
// res = write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
// (void)res;
|
|
||||||
|
|
||||||
close(uart0_filestream);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> encode(const std::vector<uint8_t> & data)
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> result;
|
|
||||||
|
|
||||||
uint8_t previousByte = 0x00;
|
|
||||||
uint8_t nextByte = 0x00;
|
|
||||||
for (unsigned iData=0; iData<data.size(); iData+=3)
|
|
||||||
{
|
|
||||||
const uint8_t byte1 = data[iData];
|
|
||||||
const uint8_t byte2 = data[iData+1];
|
|
||||||
const uint8_t byte3 = data[iData+2];
|
|
||||||
|
|
||||||
uint8_t encByte;
|
|
||||||
encByte = encode(byte1 & 0x80, byte1 & 0x40, byte1 & 0x20);
|
|
||||||
std::cout << "Encoded byte 1: "; print(encByte); std::cout << std::endl;
|
|
||||||
split(encByte, previousByte, nextByte);
|
|
||||||
result.push_back(previousByte);
|
|
||||||
previousByte = nextByte;
|
|
||||||
|
|
||||||
encByte = encode(byte1 & 0x10, byte1 & 0x08, byte1 & 0x04);
|
|
||||||
split(encByte, previousByte, nextByte);
|
|
||||||
result.push_back(previousByte);
|
|
||||||
previousByte = nextByte;
|
|
||||||
|
|
||||||
encByte = encode(byte1 & 0x02, byte1 & 0x01, byte2 & 0x80);
|
|
||||||
split(encByte, previousByte, nextByte);
|
|
||||||
result.push_back(previousByte);
|
|
||||||
previousByte = nextByte;
|
|
||||||
|
|
||||||
encByte = encode(byte2 & 0x40, byte2 & 0x20, byte2 & 0x10);
|
|
||||||
split(encByte, previousByte, nextByte);
|
|
||||||
result.push_back(previousByte);
|
|
||||||
previousByte = nextByte;
|
|
||||||
|
|
||||||
encByte = encode(byte2 & 0x08, byte2 & 0x04, byte2 & 0x02);
|
|
||||||
split(encByte, previousByte, nextByte);
|
|
||||||
result.push_back(previousByte);
|
|
||||||
previousByte = nextByte;
|
|
||||||
|
|
||||||
encByte = encode(byte2 & 0x01, byte3 & 0x80, byte3 & 0x40);
|
|
||||||
split(encByte, previousByte, nextByte);
|
|
||||||
result.push_back(previousByte);
|
|
||||||
previousByte = nextByte;
|
|
||||||
|
|
||||||
encByte = encode(byte3 & 0x20, byte3 & 0x10, byte3 & 0x08);
|
|
||||||
split(encByte, previousByte, nextByte);
|
|
||||||
result.push_back(previousByte);
|
|
||||||
previousByte = nextByte;
|
|
||||||
|
|
||||||
encByte = encode(byte3 & 0x04, byte3 & 0x02, byte3 & 0x01);
|
|
||||||
split(encByte, previousByte, nextByte);
|
|
||||||
result.push_back(previousByte);
|
|
||||||
previousByte = nextByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push_back(previousByte);
|
|
||||||
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void split(const uint8_t byte, uint8_t & out1, uint8_t & out2)
|
|
||||||
{
|
|
||||||
out1 |= (byte & 0x0F) << 4;
|
|
||||||
out2 = (byte & 0xF0) >> 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t encode(const bool bit1, const bool bit2, const bool bit3)
|
|
||||||
{
|
|
||||||
if (bit2)
|
|
||||||
{
|
|
||||||
uint8_t result = 0x19; // 0--1 01 10-1
|
|
||||||
if (bit1) result |= 0x02;
|
|
||||||
// else result &= ~0x02;
|
|
||||||
|
|
||||||
if (bit3) result |= 0x60;
|
|
||||||
// else result &= ~0x60;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uint8_t result = 0x21;// 0x21 (0-10 01 0--1)
|
|
||||||
if (bit1) result |= 0x06;
|
|
||||||
// else result &= ~0x06;
|
|
||||||
|
|
||||||
if (bit3) result |= 0x40;
|
|
||||||
// else result &= ~0x40;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,188 +0,0 @@
|
|||||||
|
|
||||||
// STL includes
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include <unistd.h> //Used for UART
|
|
||||||
#include <fcntl.h> //Used for UART
|
|
||||||
#include <termios.h> //Used for UART
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
|
|
||||||
std::vector<uint8_t> encode(const std::vector<uint8_t> & data);
|
|
||||||
uint8_t encode(const bool bit1, const bool bit2, const bool bit3);
|
|
||||||
|
|
||||||
void printClockSignal(const std::vector<uint8_t> & signal)
|
|
||||||
{
|
|
||||||
bool prevBit = true;
|
|
||||||
bool nextBit = true;
|
|
||||||
|
|
||||||
for (uint8_t byte : signal)
|
|
||||||
{
|
|
||||||
|
|
||||||
for (int i=-1; i<9; ++i)
|
|
||||||
{
|
|
||||||
if (i == -1) // Start bit
|
|
||||||
nextBit = true;
|
|
||||||
else if (i == 8) // Stop bit
|
|
||||||
nextBit = false;
|
|
||||||
else
|
|
||||||
nextBit = ~byte & (1 << i);
|
|
||||||
|
|
||||||
if (!prevBit && nextBit)
|
|
||||||
{
|
|
||||||
std::cout << ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextBit)
|
|
||||||
std::cout << '1';
|
|
||||||
else
|
|
||||||
std::cout << '0';
|
|
||||||
|
|
||||||
prevBit = nextBit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
const std::vector<uint8_t> white{0xff,0xff,0xff, 0xff,0xff,0xff, 0xff,0xff,0xff};
|
|
||||||
const std::vector<uint8_t> green{0xff, 0x00, 0x00};
|
|
||||||
const std::vector<uint8_t> red {0x00, 0xff, 0x00};
|
|
||||||
const std::vector<uint8_t> blue {0x00, 0x00, 0xff};
|
|
||||||
const std::vector<uint8_t> cyan {0xff, 0x00, 0xff};
|
|
||||||
const std::vector<uint8_t> mix {0x55, 0x55, 0x55};
|
|
||||||
const std::vector<uint8_t> black{0x00, 0x00, 0x00};
|
|
||||||
const std::vector<uint8_t> gray{0x01, 0x01, 0x01};
|
|
||||||
|
|
||||||
// printClockSignal(encode(mix));std::cout << std::endl;
|
|
||||||
|
|
||||||
//OPEN THE UART
|
|
||||||
// int uart0_filestream = open("/dev/ttyAMA0", O_WRONLY | O_NOCTTY | O_NDELAY);
|
|
||||||
int uart0_filestream = open("/dev/ttyUSB0", O_WRONLY | O_NOCTTY | O_NDELAY);
|
|
||||||
if (uart0_filestream == -1)
|
|
||||||
{
|
|
||||||
//ERROR - CAN'T OPEN SERIAL PORT
|
|
||||||
printf("Error - Unable to open UART. Ensure it is not in use by another application\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the port
|
|
||||||
struct termios options;
|
|
||||||
tcgetattr(uart0_filestream, &options);
|
|
||||||
options.c_cflag = B2500000 | CS8 | CLOCAL;
|
|
||||||
options.c_iflag = IGNPAR;
|
|
||||||
options.c_oflag = 0;
|
|
||||||
options.c_lflag = 0;
|
|
||||||
|
|
||||||
tcflush(uart0_filestream, TCIFLUSH);
|
|
||||||
tcsetattr(uart0_filestream, TCSANOW, &options);
|
|
||||||
|
|
||||||
{
|
|
||||||
getchar();
|
|
||||||
const std::vector<uint8_t> encGreenData = encode(green);
|
|
||||||
const std::vector<uint8_t> encBlueData = encode(blue);
|
|
||||||
const std::vector<uint8_t> encRedData = encode(red);
|
|
||||||
const std::vector<uint8_t> encGrayData = encode(gray);
|
|
||||||
const std::vector<uint8_t> encBlackData = encode(black);
|
|
||||||
|
|
||||||
//std::cout << "Writing GREEN ("; printClockSignal(encode(green)); std::cout << ")" << std::endl;
|
|
||||||
// const std::vector<uint8_t> garbage {0x0f};
|
|
||||||
// write(uart0_filestream, garbage.data(), garbage.size());
|
|
||||||
// write(uart0_filestream, encGreenData.data(), encGreenData.size());
|
|
||||||
// write(uart0_filestream, encRedData.data(), encRedData.size());
|
|
||||||
// write(uart0_filestream, encBlueData.data(), encBlueData.size());
|
|
||||||
// write(uart0_filestream, encGrayData.data(), encGrayData.size());
|
|
||||||
// write(uart0_filestream, encBlackData.data(), encBlackData.size());
|
|
||||||
// }
|
|
||||||
// {
|
|
||||||
// getchar();
|
|
||||||
const std::vector<uint8_t> encData = encode(white);
|
|
||||||
std::cout << "Writing WHITE ("; printClockSignal(encode(white)); std::cout << ")" << std::endl;
|
|
||||||
// const std::vector<uint8_t> garbage {0x0f};
|
|
||||||
// write(uart0_filestream, garbage.data(), garbage.size());
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
}
|
|
||||||
{
|
|
||||||
getchar();
|
|
||||||
const std::vector<uint8_t> encData = encode(green);
|
|
||||||
std::cout << "Writing GREEN ("; printClockSignal(encode(green)); std::cout << ")" << std::endl;
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
}
|
|
||||||
{
|
|
||||||
getchar();
|
|
||||||
const std::vector<uint8_t> encData = encode(red);
|
|
||||||
std::cout << "Writing RED ("; printClockSignal(encode(red)); std::cout << ")" << std::endl;
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
}
|
|
||||||
{
|
|
||||||
getchar();
|
|
||||||
const std::vector<uint8_t> encData = encode(blue);
|
|
||||||
std::cout << "Writing BLUE ("; printClockSignal(encode(blue)); std::cout << ")" << std::endl;
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
}
|
|
||||||
{
|
|
||||||
getchar();
|
|
||||||
const std::vector<uint8_t> encData = encode(cyan);
|
|
||||||
std::cout << "Writing CYAN? ("; printClockSignal(encode(cyan)); std::cout << ")" << std::endl;
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
}
|
|
||||||
{
|
|
||||||
getchar();
|
|
||||||
const std::vector<uint8_t> encData = encode(mix);
|
|
||||||
std::cout << "Writing MIX ("; printClockSignal(encode(mix)); std::cout << ")" << std::endl;
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
}
|
|
||||||
{
|
|
||||||
getchar();
|
|
||||||
const std::vector<uint8_t> encData = encode(black);
|
|
||||||
std::cout << "Writing BLACK ("; printClockSignal(encode(black)); std::cout << ")" << std::endl;
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
write(uart0_filestream, encData.data(), encData.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
close(uart0_filestream);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> encode(const std::vector<uint8_t> & data)
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> result;
|
|
||||||
for (size_t iByte=0; iByte<data.size(); iByte+=3)
|
|
||||||
{
|
|
||||||
const uint8_t byte1 = data[iByte];
|
|
||||||
const uint8_t byte2 = data[iByte+1];
|
|
||||||
const uint8_t byte3 = data[iByte+2];
|
|
||||||
|
|
||||||
result.push_back(encode(byte1 & 0x80, byte1 & 0x40, byte1 & 0x20));
|
|
||||||
result.push_back(encode(byte1 & 0x10, byte1 & 0x08, byte1 & 0x04));
|
|
||||||
result.push_back(encode(byte1 & 0x02, byte1 & 0x01, byte2 & 0x80));
|
|
||||||
result.push_back(encode(byte2 & 0x40, byte2 & 0x20, byte2 & 0x10));
|
|
||||||
result.push_back(encode(byte2 & 0x08, byte2 & 0x04, byte2 & 0x02));
|
|
||||||
result.push_back(encode(byte2 & 0x01, byte3 & 0x80, byte3 & 0x40));
|
|
||||||
result.push_back(encode(byte3 & 0x20, byte3 & 0x10, byte3 & 0x08));
|
|
||||||
result.push_back(encode(byte3 & 0x04, byte3 & 0x02, byte3 & 0x01));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t encode(const bool bit1, const bool bit2, const bool bit3)
|
|
||||||
{
|
|
||||||
uint8_t result = 0x44; // 0100 0100
|
|
||||||
|
|
||||||
if (bit1)
|
|
||||||
result |= 0x01; // 0000 0001
|
|
||||||
|
|
||||||
if (bit2)
|
|
||||||
result |= 0x18; // 0001 1000
|
|
||||||
|
|
||||||
if (bit3)
|
|
||||||
result |= 0x80; // 1000 0000
|
|
||||||
|
|
||||||
return ~result;
|
|
||||||
}
|
|
@ -1,260 +0,0 @@
|
|||||||
|
|
||||||
// STL includes
|
|
||||||
#include <iostream>
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
// Serialport includes
|
|
||||||
#include <serial/serial.h>
|
|
||||||
|
|
||||||
int testSerialPortLib();
|
|
||||||
int testHyperionDevice(int argc, char** argv);
|
|
||||||
int testWs2812bDevice();
|
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
|
||||||
{
|
|
||||||
// if (argc == 1)
|
|
||||||
// {
|
|
||||||
// return testSerialPortLib();
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// return testHyperionDevice(argc, argv);
|
|
||||||
// }
|
|
||||||
return testWs2812bDevice();
|
|
||||||
}
|
|
||||||
|
|
||||||
int testSerialPortLib()
|
|
||||||
{
|
|
||||||
serial::Serial rs232Port("/dev/ttyAMA0", 4000000);
|
|
||||||
|
|
||||||
std::default_random_engine generator;
|
|
||||||
std::uniform_int_distribution<int> distribution(1,2);
|
|
||||||
|
|
||||||
std::vector<uint8_t> data;
|
|
||||||
for (int i=0; i<9; ++i)
|
|
||||||
{
|
|
||||||
int coinFlip = distribution(generator);
|
|
||||||
if (coinFlip == 1)
|
|
||||||
{
|
|
||||||
data.push_back(0xCE);
|
|
||||||
data.push_back(0xCE);
|
|
||||||
data.push_back(0xCE);
|
|
||||||
data.push_back(0xCE);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
data.push_back(0x8C);
|
|
||||||
data.push_back(0x8C);
|
|
||||||
data.push_back(0x8C);
|
|
||||||
data.push_back(0x8C);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::cout << "Type 'c' to continue, 'q' or 'x' to quit: ";
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
char c = getchar();
|
|
||||||
if (c == 'q' || c == 'x')
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (c != 'c')
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
rs232Port.flushOutput();
|
|
||||||
rs232Port.write(data);
|
|
||||||
rs232Port.flush();
|
|
||||||
|
|
||||||
data.clear();
|
|
||||||
for (int i=0; i<9; ++i)
|
|
||||||
{
|
|
||||||
int coinFlip = distribution(generator);
|
|
||||||
if (coinFlip == 1)
|
|
||||||
{
|
|
||||||
data.push_back(0xCE);
|
|
||||||
data.push_back(0xCE);
|
|
||||||
data.push_back(0xCE);
|
|
||||||
data.push_back(0xCE);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
data.push_back(0x8C);
|
|
||||||
data.push_back(0x8C);
|
|
||||||
data.push_back(0x8C);
|
|
||||||
data.push_back(0x8C);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
rs232Port.close();
|
|
||||||
}
|
|
||||||
catch (const std::runtime_error& excp)
|
|
||||||
{
|
|
||||||
std::cout << "Caught exception: " << excp.what() << std::endl;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "../libsrc/leddevice/LedRs232Device.h"
|
|
||||||
|
|
||||||
class TestDevice : public LedRs232Device
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TestDevice() :
|
|
||||||
LedRs232Device("/dev/ttyAMA0", 4000000)
|
|
||||||
{
|
|
||||||
open();
|
|
||||||
}
|
|
||||||
|
|
||||||
int write(const std::vector<ColorRgb> &ledValues)
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> bytes(ledValues.size() * 3 * 4);
|
|
||||||
|
|
||||||
uint8_t * bytePtr = bytes.data();
|
|
||||||
for (ColorRgb color : ledValues)
|
|
||||||
{
|
|
||||||
byte2Signal(color.green, bytePtr);
|
|
||||||
bytePtr += 4;
|
|
||||||
byte2Signal(color.red, bytePtr);
|
|
||||||
bytePtr += 4;
|
|
||||||
byte2Signal(color.blue, bytePtr);
|
|
||||||
bytePtr += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeBytes(bytes.size(), bytes.data());
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int switchOff() { return 0; };
|
|
||||||
|
|
||||||
void writeTestSequence(const std::vector<uint8_t> & data)
|
|
||||||
{
|
|
||||||
writeBytes(data.size(), data.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
void byte2Signal(const uint8_t byte, uint8_t * output)
|
|
||||||
{
|
|
||||||
output[0] = bits2Signal(byte & 0x80, byte & 0x40);
|
|
||||||
output[1] = bits2Signal(byte & 0x20, byte & 0x10);
|
|
||||||
output[2] = bits2Signal(byte & 0x08, byte & 0x04);
|
|
||||||
output[3] = bits2Signal(byte & 0x02, byte & 0x01);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t bits2Signal(const bool bit1, const bool bit2)
|
|
||||||
{
|
|
||||||
if (bit1)
|
|
||||||
{
|
|
||||||
if (bit2)
|
|
||||||
{
|
|
||||||
return 0x8C;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0xCC;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (bit2)
|
|
||||||
{
|
|
||||||
return 0x8E;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0xCE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0x00;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int testHyperionDevice(int argc, char** argv)
|
|
||||||
{
|
|
||||||
TestDevice rs232Device;
|
|
||||||
|
|
||||||
if (argc > 1 && strncmp(argv[1], "off", 3) == 0)
|
|
||||||
{
|
|
||||||
rs232Device.write(std::vector<ColorRgb>(150, {0, 0, 0}));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int loopCnt = 0;
|
|
||||||
|
|
||||||
std::cout << "Type 'c' to continue, 'q' or 'x' to quit: ";
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
char c = getchar();
|
|
||||||
if (c == 'q' || c == 'x')
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (c != 'c')
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
rs232Device.write(std::vector<ColorRgb>(loopCnt, {255, 255, 255}));
|
|
||||||
|
|
||||||
++loopCnt;
|
|
||||||
}
|
|
||||||
|
|
||||||
rs232Device.write(std::vector<ColorRgb>(150, {0, 0, 0}));
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "../libsrc/leddevice/LedDeviceWs2812b.h"
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
int testWs2812bDevice()
|
|
||||||
{
|
|
||||||
LedDeviceWs2812b device;
|
|
||||||
device.open();
|
|
||||||
|
|
||||||
std::cout << "Type 'c' to continue, 'q' or 'x' to quit: ";
|
|
||||||
int loopCnt = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// char c = getchar();
|
|
||||||
// if (c == 'q' || c == 'x')
|
|
||||||
// {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// if (c != 'c')
|
|
||||||
// {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (loopCnt%4 == 0)
|
|
||||||
device.write(std::vector<ColorRgb>(25, {255, 0, 0}));
|
|
||||||
else if (loopCnt%4 == 1)
|
|
||||||
device.write(std::vector<ColorRgb>(25, {0, 255, 0}));
|
|
||||||
else if (loopCnt%4 == 2)
|
|
||||||
device.write(std::vector<ColorRgb>(25, {0, 0, 255}));
|
|
||||||
else if (loopCnt%4 == 3)
|
|
||||||
device.write(std::vector<ColorRgb>(25, {17, 188, 66}));
|
|
||||||
|
|
||||||
++loopCnt;
|
|
||||||
|
|
||||||
usleep(200000);
|
|
||||||
if (loopCnt > 200)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
device.write(std::vector<ColorRgb>(150, {0, 0, 0}));
|
|
||||||
device.switchOff();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,387 +0,0 @@
|
|||||||
|
|
||||||
#include <random>
|
|
||||||
#include <iostream>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h> //Used for UART
|
|
||||||
#include <fcntl.h> //Used for UART
|
|
||||||
#include <termios.h> //Used for UART
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
|
|
||||||
#include <linux/serial.h>
|
|
||||||
|
|
||||||
#include <csignal>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <bitset>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <sched.h>
|
|
||||||
|
|
||||||
void set_realtime_priority() {
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
// We'll operate on the currently running thread.
|
|
||||||
pthread_t this_thread = pthread_self();
|
|
||||||
// struct sched_param is used to store the scheduling priority
|
|
||||||
struct sched_param params;
|
|
||||||
// We'll set the priority to the maximum.
|
|
||||||
params.sched_priority = sched_get_priority_max(SCHED_FIFO);
|
|
||||||
std::cout << "Trying to set thread realtime prio = " << params.sched_priority << std::endl;
|
|
||||||
|
|
||||||
// Attempt to set thread real-time priority to the SCHED_FIFO policy
|
|
||||||
ret = pthread_setschedparam(this_thread, SCHED_FIFO, ¶ms);
|
|
||||||
if (ret != 0) {
|
|
||||||
// Print the error
|
|
||||||
std::cout << "Unsuccessful in setting thread realtime prio (erno=" << ret << ")" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now verify the change in thread priority
|
|
||||||
int policy = 0;
|
|
||||||
ret = pthread_getschedparam(this_thread, &policy, ¶ms);
|
|
||||||
if (ret != 0) {
|
|
||||||
std::cout << "Couldn't retrieve real-time scheduling paramers" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the correct policy was applied
|
|
||||||
if(policy != SCHED_FIFO) {
|
|
||||||
std::cout << "Scheduling is NOT SCHED_FIFO!" << std::endl;
|
|
||||||
} else {
|
|
||||||
std::cout << "SCHED_FIFO OK" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print thread scheduling priority
|
|
||||||
std::cout << "Thread priority is " << params.sched_priority << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct ColorSignal
|
|
||||||
{
|
|
||||||
uint8_t green_1;
|
|
||||||
uint8_t green_2;
|
|
||||||
uint8_t green_3;
|
|
||||||
uint8_t green_4;
|
|
||||||
|
|
||||||
uint8_t red_1;
|
|
||||||
uint8_t red_2;
|
|
||||||
uint8_t red_3;
|
|
||||||
uint8_t red_4;
|
|
||||||
|
|
||||||
uint8_t blue_1;
|
|
||||||
uint8_t blue_2;
|
|
||||||
uint8_t blue_3;
|
|
||||||
uint8_t blue_4;
|
|
||||||
};
|
|
||||||
|
|
||||||
static ColorSignal RED_Signal = { 0xCE, 0xCE, 0xCE, 0xCE,
|
|
||||||
0xCE, 0x8C, 0x8C, 0x8C,
|
|
||||||
0xCE, 0xCE, 0xCE, 0xCE };
|
|
||||||
static ColorSignal GREEN_Signal = { 0xCE, 0x8C, 0x8C, 0x8C,
|
|
||||||
0xCE, 0xCE, 0xCE, 0xCE,
|
|
||||||
0xCE, 0xCE, 0xCE, 0xCE };
|
|
||||||
static ColorSignal BLUE_Signal = { 0xCE, 0xCE, 0xCE, 0xCE,
|
|
||||||
0xCE, 0xCE, 0xCE, 0xCE,
|
|
||||||
0xCE, 0x8C, 0x8C, 0x8C};
|
|
||||||
static ColorSignal BLACK_Signal = { 0xCE, 0xCE, 0xCE, 0xCE,
|
|
||||||
0xCE, 0xCE, 0xCE, 0xCE,
|
|
||||||
0xCE, 0xCE, 0xCE, 0xCE};
|
|
||||||
|
|
||||||
static volatile bool _running;
|
|
||||||
|
|
||||||
void signal_handler(int signum)
|
|
||||||
{
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void test3bitsEncoding();
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
if (true)
|
|
||||||
{
|
|
||||||
test3bitsEncoding();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_running = true;
|
|
||||||
signal(SIGTERM, &signal_handler);
|
|
||||||
|
|
||||||
//-------------------------
|
|
||||||
//----- SETUP USART 0 -----
|
|
||||||
//-------------------------
|
|
||||||
//At bootup, pins 8 and 10 are already set to UART0_TXD, UART0_RXD (ie the alt0 function) respectively
|
|
||||||
int uart0_filestream = -1;
|
|
||||||
|
|
||||||
//OPEN THE UART
|
|
||||||
//The flags (defined in fcntl.h):
|
|
||||||
// Access modes (use 1 of these):
|
|
||||||
// O_RDONLY - Open for reading only.
|
|
||||||
// O_RDWR - Open for reading and writing.
|
|
||||||
// O_WRONLY - Open for writing only.
|
|
||||||
//
|
|
||||||
// O_NDELAY / O_NONBLOCK (same function) - Enables nonblocking mode. When set read requests on the file can return immediately with a failure status
|
|
||||||
// if there is no input immediately available (instead of blocking). Likewise, write requests can also return
|
|
||||||
// immediately with a failure status if the output can't be written immediately.
|
|
||||||
//
|
|
||||||
// O_NOCTTY - When set and path identifies a terminal device, open() shall not cause the terminal device to become the controlling terminal for the process.
|
|
||||||
uart0_filestream = open("/dev/ttyAMA0", O_WRONLY | O_NOCTTY | O_NDELAY); //Open in non blocking read/write mode
|
|
||||||
if (uart0_filestream == -1)
|
|
||||||
{
|
|
||||||
//ERROR - CAN'T OPEN SERIAL PORT
|
|
||||||
printf("Error - Unable to open UART. Ensure it is not in use by another application\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (0)
|
|
||||||
{
|
|
||||||
//CONFIGURE THE UART
|
|
||||||
//The flags (defined in /usr/include/termios.h - see http://pubs.opengroup.org/onlinepubs/007908799/xsh/termios.h.html):
|
|
||||||
// Baud rate:- B1200, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400, B460800, B500000, B576000, B921600, B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000, B4000000
|
|
||||||
// CSIZE:- CS5, CS6, CS7, CS8
|
|
||||||
// CLOCAL - Ignore modem status lines
|
|
||||||
// CREAD - Enable receiver
|
|
||||||
// IGNPAR = Ignore characters with parity errors
|
|
||||||
// ICRNL - Map CR to NL on input (Use for ASCII comms where you want to auto correct end of line characters - don't use for bianry comms!)
|
|
||||||
// PARENB - Parity enable
|
|
||||||
// PARODD - Odd parity (else even)
|
|
||||||
struct termios options;
|
|
||||||
tcgetattr(uart0_filestream, &options);
|
|
||||||
options.c_cflag = B4000000 | CS8 | CLOCAL; //<Set baud rate
|
|
||||||
options.c_iflag = IGNPAR;
|
|
||||||
options.c_oflag = 0;
|
|
||||||
options.c_lflag = 0;
|
|
||||||
cfmakeraw(&options);
|
|
||||||
|
|
||||||
std::cout << "options.c_cflag = " << options.c_cflag << std::endl;
|
|
||||||
std::cout << "options.c_iflag = " << options.c_iflag << std::endl;
|
|
||||||
std::cout << "options.c_oflag = " << options.c_oflag << std::endl;
|
|
||||||
std::cout << "options.c_lflag = " << options.c_lflag << std::endl;
|
|
||||||
|
|
||||||
tcflush(uart0_filestream, TCIFLUSH);
|
|
||||||
tcsetattr(uart0_filestream, TCSANOW, &options);
|
|
||||||
// Let's verify configured options
|
|
||||||
tcgetattr(uart0_filestream, &options);
|
|
||||||
|
|
||||||
std::cout << "options.c_cflag = " << options.c_cflag << std::endl;
|
|
||||||
std::cout << "options.c_iflag = " << options.c_iflag << std::endl;
|
|
||||||
std::cout << "options.c_oflag = " << options.c_oflag << std::endl;
|
|
||||||
std::cout << "options.c_lflag = " << options.c_lflag << std::endl;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
struct serial_struct ser;
|
|
||||||
|
|
||||||
if (-1 == ioctl(uart0_filestream, TIOCGSERIAL, &ser))
|
|
||||||
{
|
|
||||||
std::cerr << "Failed to obtian 'serial_struct' for setting custom baudrate" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "Current divisor: " << ser.custom_divisor << " ( = " << ser.baud_base << " / 4000000" << std::endl;
|
|
||||||
|
|
||||||
// set custom divisor
|
|
||||||
ser.custom_divisor = ser.baud_base / 8000000;
|
|
||||||
// update flags
|
|
||||||
ser.flags &= ~ASYNC_SPD_MASK;
|
|
||||||
ser.flags |= ASYNC_SPD_CUST;
|
|
||||||
|
|
||||||
std::cout << "Current divisor: " << ser.custom_divisor << " ( = " << ser.baud_base << " / 8000000" << std::endl;
|
|
||||||
|
|
||||||
|
|
||||||
if (-1 == ioctl(uart0_filestream, TIOCSSERIAL, &ser))
|
|
||||||
{
|
|
||||||
std::cerr << "Failed to configure 'serial_struct' for setting custom baudrate" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check result
|
|
||||||
if (-1 == ioctl(uart0_filestream, TIOCGSERIAL, &ser))
|
|
||||||
{
|
|
||||||
std::cerr << "Failed to obtian 'serial_struct' for setting custom baudrate" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "Current divisor: " << ser.custom_divisor << " ( = " << ser.baud_base << " / 4000000" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (uart0_filestream < 0)
|
|
||||||
{
|
|
||||||
std::cerr << "Opening the device has failed" << std::endl;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//----- TX BYTES -----
|
|
||||||
std::vector<ColorSignal> signalData(10, RED_Signal);
|
|
||||||
|
|
||||||
int loopCnt = 0;
|
|
||||||
std::cout << "Type 'c' to continue, 'q' or 'x' to quit: ";
|
|
||||||
while (_running)
|
|
||||||
{
|
|
||||||
char c = getchar();
|
|
||||||
if (c == 'q' || c == 'x')
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (c != 'c')
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_realtime_priority();
|
|
||||||
for (int iRun=0; iRun<10; ++iRun)
|
|
||||||
{
|
|
||||||
// tcflush(uart0_filestream, TCOFLUSH);
|
|
||||||
write(uart0_filestream, signalData.data(), signalData.size()*sizeof(ColorSignal));
|
|
||||||
tcdrain(uart0_filestream);
|
|
||||||
|
|
||||||
usleep(100000);
|
|
||||||
++loopCnt;
|
|
||||||
|
|
||||||
if (loopCnt%3 == 2)
|
|
||||||
signalData = std::vector<ColorSignal>(10, GREEN_Signal);
|
|
||||||
else if(loopCnt%3 == 1)
|
|
||||||
signalData = std::vector<ColorSignal>(10, BLUE_Signal);
|
|
||||||
else if(loopCnt%3 == 0)
|
|
||||||
signalData = std::vector<ColorSignal>(10, RED_Signal);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
signalData = std::vector<ColorSignal>(50, BLACK_Signal);
|
|
||||||
write(uart0_filestream, signalData.data(), signalData.size()*sizeof(ColorSignal));
|
|
||||||
//----- CLOSE THE UART -----
|
|
||||||
close(uart0_filestream);
|
|
||||||
|
|
||||||
std::cout << "Program finished" << std::endl;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> bit3Encode(const std::vector<uint8_t> & bytes);
|
|
||||||
uint8_t bit3Encode(const bool bit_1, const bool bit_2, const bool bit_3);
|
|
||||||
|
|
||||||
void test3bitsEncoding()
|
|
||||||
{
|
|
||||||
//OPEN THE UART
|
|
||||||
// int uart0_filestream = open("/dev/ttyAMA0", O_WRONLY | O_NOCTTY | O_NDELAY);
|
|
||||||
int uart0_filestream = open("/dev/ttyUSB0", O_WRONLY | O_NOCTTY | O_NDELAY);
|
|
||||||
if (uart0_filestream == -1)
|
|
||||||
{
|
|
||||||
//ERROR - CAN'T OPEN SERIAL PORT
|
|
||||||
printf("Error - Unable to open UART. Ensure it is not in use by another application\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the port
|
|
||||||
struct termios options;
|
|
||||||
tcgetattr(uart0_filestream, &options);
|
|
||||||
options.c_cflag = B2500000 | CS7 | CLOCAL;
|
|
||||||
options.c_iflag = IGNPAR;
|
|
||||||
options.c_oflag = 0;
|
|
||||||
options.c_lflag = 0;
|
|
||||||
|
|
||||||
tcflush(uart0_filestream, TCIFLUSH);
|
|
||||||
tcsetattr(uart0_filestream, TCSANOW, &options);
|
|
||||||
|
|
||||||
std::vector<uint8_t> colorRed;
|
|
||||||
for (unsigned i=0; i<10; ++i)
|
|
||||||
{
|
|
||||||
colorRed.push_back(0x00);
|
|
||||||
colorRed.push_back(0xFF);
|
|
||||||
colorRed.push_back(0x00);
|
|
||||||
}
|
|
||||||
std::vector<uint8_t> colorGreen;
|
|
||||||
for (unsigned i=0; i<10; ++i)
|
|
||||||
{
|
|
||||||
colorGreen.push_back(0xFF);
|
|
||||||
colorGreen.push_back(0x00);
|
|
||||||
colorGreen.push_back(0x00);
|
|
||||||
}
|
|
||||||
std::vector<uint8_t> colorBlue;
|
|
||||||
for (unsigned i=0; i<10; ++i)
|
|
||||||
{
|
|
||||||
colorBlue.push_back(0x00);
|
|
||||||
colorBlue.push_back(0x00);
|
|
||||||
colorBlue.push_back(0xFF);
|
|
||||||
}
|
|
||||||
std::vector<uint8_t> colorBlack;
|
|
||||||
for (unsigned i=0; i<10; ++i)
|
|
||||||
{
|
|
||||||
colorBlack.push_back(0x00);
|
|
||||||
colorBlack.push_back(0x00);
|
|
||||||
colorBlack.push_back(0x00);
|
|
||||||
}
|
|
||||||
const std::vector<uint8_t> colorRedSignal = bit3Encode(colorRed);
|
|
||||||
const std::vector<uint8_t> colorGreenSignal = bit3Encode(colorGreen);
|
|
||||||
const std::vector<uint8_t> colorBlueSignal = bit3Encode(colorBlue);
|
|
||||||
const std::vector<uint8_t> colorBlackSignal = bit3Encode(colorBlack);
|
|
||||||
|
|
||||||
for (unsigned i=0; i<100; ++i)
|
|
||||||
{
|
|
||||||
size_t res;
|
|
||||||
res = write(uart0_filestream, colorRedSignal.data(), colorRedSignal.size());
|
|
||||||
(void)res;
|
|
||||||
usleep(100000);
|
|
||||||
res = write(uart0_filestream, colorGreenSignal.data(), colorGreenSignal.size());
|
|
||||||
(void)res;
|
|
||||||
usleep(100000);
|
|
||||||
res = write(uart0_filestream, colorBlueSignal.data(), colorBlueSignal.size());
|
|
||||||
(void)res;
|
|
||||||
usleep(100000);
|
|
||||||
}
|
|
||||||
size_t res = write(uart0_filestream, colorBlackSignal.data(), colorBlackSignal.size());
|
|
||||||
(void)res;
|
|
||||||
//----- CLOSE THE UART -----
|
|
||||||
res = close(uart0_filestream);
|
|
||||||
(void)res;
|
|
||||||
|
|
||||||
std::cout << "Program finished" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> bit3Encode(const std::vector<uint8_t> & bytes)
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> result;
|
|
||||||
|
|
||||||
for (unsigned iByte=0; iByte<bytes.size(); iByte+=3)
|
|
||||||
{
|
|
||||||
const uint8_t & byte1 = bytes[iByte];
|
|
||||||
const uint8_t & byte2 = bytes[iByte + 1];
|
|
||||||
const uint8_t & byte3 = bytes[iByte + 2];
|
|
||||||
|
|
||||||
result.push_back(bit3Encode(byte1 & 0x80, byte1 & 0x40, byte1 & 0x20));
|
|
||||||
result.push_back(bit3Encode(byte1 & 0x10, byte1 & 0x08, byte1 & 0x04));
|
|
||||||
result.push_back(bit3Encode(byte1 & 0x02, byte1 & 0x01, byte2 & 0x80));
|
|
||||||
result.push_back(bit3Encode(byte2 & 0x40, byte2 & 0x20, byte2 & 0x10));
|
|
||||||
result.push_back(bit3Encode(byte2 & 0x08, byte2 & 0x04, byte2 & 0x02));
|
|
||||||
result.push_back(bit3Encode(byte2 & 0x01, byte3 & 0x80, byte3 & 0x40));
|
|
||||||
result.push_back(bit3Encode(byte3 & 0x20, byte3 & 0x10, byte3 & 0x08));
|
|
||||||
result.push_back(bit3Encode(byte3 & 0x04, byte3 & 0x02, byte3 & 0x01));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t bit3Encode(const bool bit_1, const bool bit_2, const bool bit_3)
|
|
||||||
{
|
|
||||||
// Bit index(default):1 2 3
|
|
||||||
// | | |
|
|
||||||
// default value (1) 00 100 10 (0)
|
|
||||||
//
|
|
||||||
// Reversed value (1) 01 001 00 (0)
|
|
||||||
// | | |
|
|
||||||
// Bit index (rev): 3 2 1
|
|
||||||
uint8_t result = 0x24;
|
|
||||||
|
|
||||||
if(bit_1)
|
|
||||||
{
|
|
||||||
result |= 0x01;
|
|
||||||
}
|
|
||||||
if (bit_2)
|
|
||||||
{
|
|
||||||
result |= 0x08;
|
|
||||||
}
|
|
||||||
if (bit_3)
|
|
||||||
{
|
|
||||||
result |= 0x40;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ~result;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user