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

Add VXI-11 server and improve robustness of socket server #52

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@
.venv/
__pycache__
*.pyc
.vscode/extensions.json
.vscode/settings.json
src/hislip_server/
pcap/
72 changes: 48 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ provides Modbus TCP and SCPI support as well as a web interface.
The firmware has been tested with various tools and libraries:

- Riden Hardware
- RD6006
- RD6006 and RD6012
- Riden Firmware
- Riden v1.28
- Riden v1.41
Expand All @@ -19,31 +19,57 @@ The firmware has been tested with various tools and libraries:
- SCPI
- [lxi-tools](https://github.com/lxi-tools/lxi-tools)
- [EEZ Studio](https://www.envox.eu/studio/studio-introduction/)

- [PyVISA](https://pyvisa.readthedocs.io/)

## Features

- Modbus RTU client communicating with Riden power supply firmware.
- Modbus TCP bridge.
- SCPI control.
- SCPI control
- via raw socket (VISA string: `TCPIP::<ip address>::5025::SOCKET`)
- and via vxi-11 (VISA string: `TCPIP::<ip address>::INSTR`).
- Web interface to configure the dongle and update firmware.
- Automatically set power supply clock based on NTP.
- mDNS advertising.
- Handles approximately 65 queries/second using Modbus TCP or SCPI
- Handles approximately 65 queries/second using Modbus TCP or raw socket SCPI
(tested using Unisoft v1.41.1k, UART baudrate set at 921600).


## Warning

- When flashing the Riden WiFi module you _will_ erase the existing firmware.
- The firmware provided in this repository comes with no warranty.


## Query Performance

The regular Riden power supply firmware is considerably slower than UniSoft,
handling less than 10 queries/second.

## VISA communication directives

An example test program can be found under [/scripts/test_pyvisa.py](/scripts/test_pyvisa.py)

### VXI-11

The VXI-11 channel (`TCPIP::<ip address>::INSTR`) is auto discoverable via mDNS, TCP and UDP, making it highly compatible with most tools.

While you use the VXI server, the raw socket server is disabled.

Note that when you use the web interface to kill a VXI-11 client, it will not properly inform the client. It will just kill the connection.

### Raw sockets

Raw socket capability cannot be auto discovered by pyvisa as of now. It can be discovered by lxi tools (see below)

When using the raw sockets (`TCPIP::<ip address>::5025::SOCKET`), you must, like with most other raw socket devices, use

```python
inst.read_termination = "\n"
inst.write_termination = "\n"
```

Also, be aware that when writing many commands to the device, the network layers and the device will queue them up. As a result, there can be a significant delay between the moment your client issues a command, and the moment the device handles the command. If you do not want that, insert a sleep of more than 150ms after each write command, forcing the network to send 1 command at a time. (the minimum delay depends on the configuration of your platform)

VXI-11 does not have this problem, since every command requires an ACK.

## Hardware Preparations

Expand All @@ -61,32 +87,33 @@ three additional wires: GPIO0, EN, and 3.3V. In order to ease
development you may want to terminate the wires in a Dupont header connector
allowing you to more easily use an ESP01 USB Serial Adapter or similar.


## Download the Firmware from GitHub

Firmware files will be
[released on GitHub](https://github.com/morgendagen/riden-dongle/releases)
as part of the repository.


## Compiling the Firmware

You will need [PlatformIO](https://platformio.org/) in order to
compile the
firmware.
If you want to compile, you will need [PlatformIO](https://platformio.org/) in order to
compile the firmware.

No configuration is necessary; simply execute `pio run` and wait.
The firmware is located at `.pio/build/esp12e/firmware.bin`.


## Flashing the Firmware

Provided you have prepared the hardware as described, connect
it to your computer as you would when flashing any other ESP12F module.
Provided you have prepared the hardware as described, and have either compiled, or downloaded a binary,
you must connect the dongle to your computer as you would when flashing any other ESP12F module.

You can use multiple tools to flash the firmware. The most well known are:

Execute
* platformio
* esptool (also available without installation: https://espressif.github.io/esptool-js/)

pio run -t upload --upload-port <ESP12F serial port>
Example with PlatformIO:

```pio run -t upload --upload-port <ESP12F serial port>```

and wait for the firmware to be flashed.

Expand Down Expand Up @@ -119,40 +146,38 @@ after a short while, and eventually stop. You should now
be able to connect to the dongle at
http://RDxxxx-ssssssss.local.


## Using lxi-tools to Verify Installation

Execute the command

lxi discover -m
```lxi discover -m```

to get a list of discovered SCPI devices on the network.
This firmware sneakily advertised `lxi` support in order
for lxi-tools to recognise it.

Execute the command

lxi scpi -a RDxxxx-ssssssss.local -r "*IDN?"
```lxi scpi -a RDxxxx-ssssssss.local -r "*IDN?"```

to retrieve the SCPI identification string containing
power supply model, and firmware version.

Execute the command

lxi scpi -a RDxxxx-ssssssss.local -r "VOLT?"
```lxi scpi -a RDxxxx-ssssssss.local -r "VOLT?"```

to retrieve the currently set voltage.

Invoke

lxi scpi -a RDxxxx-ssssssss.local -r "VOLT 3.3"
```lxi scpi -a RDxxxx-ssssssss.local -r "VOLT 3.3"```

to set the voltage to 3.3V

A description of the implemented commands is
available in [SCPI_COMMANDS.md](SCPI_COMMANDS.md).


## OTA firmware update

In order to update the firmware, you may prefer
Expand All @@ -163,7 +188,6 @@ it to a computer.
From the `Configure` page you can upload a
new firmware binary.


## Limitations

The Riden power supply firmware has some quirks as described
Expand Down Expand Up @@ -195,7 +219,6 @@ the register matches the language set from the front panel.

It is not possible to control keypad lock.


## Credits

- https://github.com/emelianov/modbus-esp8266
Expand All @@ -204,3 +227,4 @@ It is not possible to control keypad lock.
- https://github.com/ShayBox/Riden
- https://github.com/tzapu/WiFiManager
- https://github.com/nayarsystems/posix_tz_db
- https://github.com/awakephd/espBode
5 changes: 3 additions & 2 deletions include/riden_http_server/riden_http_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <riden_modbus/riden_modbus.h>
#include <riden_modbus_bridge/riden_modbus_bridge.h>
#include <riden_scpi/riden_scpi.h>
#include <vxi11_server/vxi_server.h>

#include <ESP8266WebServer.h>

Expand All @@ -18,7 +19,7 @@ namespace RidenDongle
class RidenHttpServer
{
public:
explicit RidenHttpServer(RidenModbus &modbus, RidenScpi &scpi, RidenModbusBridge &bridge) : modbus(modbus), scpi(scpi), bridge(bridge), server(HTTP_RAW_PORT) {}
explicit RidenHttpServer(RidenModbus &modbus, RidenScpi &scpi, RidenModbusBridge &bridge, VXI_Server &vxi_server) : modbus(modbus), scpi(scpi), bridge(bridge), vxi_server(vxi_server), server(HTTP_RAW_PORT) {}
bool begin();
void loop(void);
uint16_t port();
Expand All @@ -27,6 +28,7 @@ class RidenHttpServer
RidenModbus &modbus;
RidenScpi &scpi;
RidenModbusBridge &bridge;
VXI_Server &vxi_server;
ESP8266WebServer server;

void handle_root_get();
Expand Down Expand Up @@ -56,7 +58,6 @@ class RidenHttpServer

const char *get_firmware_version();
const char *get_serial_number();
const char *get_visa_resource();
};

} // namespace RidenDongle
6 changes: 5 additions & 1 deletion include/riden_logging/riden_logging.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
// SPDX-FileCopyrightText: 2024 Peder Toftegaard Olsen
//
// SPDX-License-Identifier: MIT

#ifdef MOCK_RIDEN
#define MODBUS_USE_SOFWARE_SERIAL
#endif
#ifdef MODBUS_USE_SOFWARE_SERIAL
#include <Arduino.h>
#define LOG(a) Serial.print(a)
#define LOG_LN(a) Serial.println(a)
#define LOG_F(...) Serial.printf(__VA_ARGS__)
#define LOG_DUMP(buf,len) for (size_t i = 0; i < len; i++) { LOG_F("%02X ", buf[i]); }
#else
#define LOG(a)
#define LOG_LN(a)
#define LOG_F(...)
#define LOG_DUMP(buf,len)
#endif
18 changes: 18 additions & 0 deletions include/riden_scpi/riden_scpi.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ class RidenScpi
uint16_t port();
std::list<IPAddress> get_connected_clients();
void disconnect_client(const IPAddress &ip);
const char *get_visa_resource();

// some inferface functions to handle commands to the SCPI parser from an outside source
// TODO: The SCPI parser should be externalised into another class and instance
bool claim_external_control() {
external_control = true;
return true; // I always gain priority
}
void release_external_control() { external_control = false; }
void write(const char *data, size_t len);
scpi_result_t read(char *data, size_t *len, size_t max_len);

private:
RidenModbus &ridenModbus;
Expand All @@ -46,6 +57,11 @@ class RidenScpi
char write_buffer[WRITE_BUFFER_LENGTH] = {};
size_t write_buffer_length = 0;

// external_control is used to indicate that the SCPI parser is handling a command from outside of the socket server
// See claim_external_control() and release_external_control()
bool external_control = false;
bool external_output_ready = false;

static const scpi_command_t scpi_commands[];
static scpi_interface_t scpi_interface;

Expand All @@ -60,6 +76,8 @@ class RidenScpi
// conventions.
static size_t SCPI_Write(scpi_t *context, const char *data, size_t len);
static scpi_result_t SCPI_Flush(scpi_t *context);
scpi_result_t SCPI_FlushRaw(void);

static int SCPI_Error(scpi_t *context, int_fast16_t err);
static scpi_result_t SCPI_Control(scpi_t *context, scpi_ctrl_name_t ctrl, scpi_reg_val_t val);
static scpi_result_t SCPI_Reset(scpi_t *context);
Expand Down
24 changes: 4 additions & 20 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ lib_deps =
build_flags =
-D DEFAULT_UART_BAUDRATE=9600
-D USE_FULL_ERROR_LIST
# -D MOCK_RIDEN
extra_scripts =
pre:scripts/get_version.py

Expand All @@ -32,25 +33,8 @@ extra_scripts =
${env.extra_scripts}
pre:scripts/get_build_time.py
scripts/make_gz.py
upload_port =
upload_resetmethod = nodemcu
upload_protocol = esptool
monitor_port = /dev/cu.usbserial-FTHJGC2Z
upload_port = /dev/cu.usbserial-FTHJGC2Z
upload_speed = 115200
monitor_port = /dev/tty.usbserial-11430
monitor_speed = 74880

[env:nodemcuv2]
board = nodemcuv2
build_flags =
${env.build_flags}
-D LED_BUILTIN=2
-D MODBUS_USE_SOFWARE_SERIAL
-D MODBUS_RX=D5 # GPIO 14
-D MODBUS_TX=D6 # GPIO 15
# -D WM_DEBUG_LEVEL=DEBUG_DEV
# -D MODBUSRTU_DEBUG
# -D MODBUSIP_DEBUG
upload_port = /dev/cu.SLAB_USBtoUART
upload_resetmethod = nodemcu
upload_speed = 115200
monitor_port = /dev/cu.SLAB_USBtoUART
monitor_speed = 74880
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
platformio==6.1.13
platformio>=6.1.13
Loading