Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ngscopeclient/scopehal-pico-bridge
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 51a4c0a176b4
Choose a base ref
...
head repository: ngscopeclient/scopehal-pico-bridge
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 87960ec021f3
Choose a head ref
  • 1 commit
  • 4 files changed
  • 1 contributor

Commits on Jan 26, 2021

  1. ps6000d can connect to the first available instrument and query infor…

    …mation. Exposes a SCPI control-plane socket which accepts *IDN? and EXIT but no other commands yet.
    azonenberg committed Jan 26, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    87960ec View commit details
Showing with 383 additions and 11 deletions.
  1. +3 −1 src/ps6000d/CMakeLists.txt
  2. +189 −0 src/ps6000d/ScpiServerThread.cpp
  3. +137 −10 src/ps6000d/main.cpp
  4. +54 −0 src/ps6000d/ps6000d.h
4 changes: 3 additions & 1 deletion src/ps6000d/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -4,11 +4,13 @@ if(WIN32)
endif()

#Set up include paths
include_directories('/opt/picoscope/include/libps6000a')
include_directories('/opt/picoscope/include/')

###############################################################################
#C++ compilation
add_executable(ps6000d
ScpiServerThread.cpp

main.cpp
)

189 changes: 189 additions & 0 deletions src/ps6000d/ScpiServerThread.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/***********************************************************************************************************************
* *
* ps6000d *
* *
* Copyright (c) 2012-2021 Andrew D. Zonenberg *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
* following conditions are met: *
* *
* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the *
* following disclaimer. *
* *
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the *
* following disclaimer in the documentation and/or other materials provided with the distribution. *
* *
* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products *
* derived from this software without specific prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
* POSSIBILITY OF SUCH DAMAGE. *
* *
***********************************************************************************************************************/

/**
@file
@author Andrew D. Zonenberg
@brief SCPI server. Control plane traffic only, no waveform data.
SCPI commands supported:
*IDN?
Returns a standard SCPI instrument identification string
EXIT
Terminates the connection
*/

#include "ps6000d.h"

using namespace std;

bool ScpiSend(Socket& sock, const string& cmd);
string ScpiRecv(Socket& sock);
void ParseScpiLine(const string& line, string& subject, string& cmd, bool& query, vector<string>& args);

/**
@brief Sends a SCPI reply (terminated by newline)
*/
bool ScpiSend(Socket& sock, const string& cmd)
{
string tempbuf = cmd + "\n";
return sock.SendLooped((unsigned char*)tempbuf.c_str(), tempbuf.length());
}

/**
@brief Reads a SCPI command (terminated by newline or semicolon)
*/
string ScpiRecv(Socket& sock)
{
char tmp = ' ';
string ret;
while(true)
{
if(!sock.RecvLooped((unsigned char*)&tmp, 1))
return "EXIT";
if( (tmp == '\n') || ( (tmp == ';') ) )
break;
else
ret += tmp;
}
return ret;
}

/**
@brief Main socket server
*/
void ScpiServerThread()
{
while(true)
{
Socket client = g_scpiSocket.Accept();
if(!client.IsValid())
break;
if(!client.DisableNagle())
LogWarning("Failed to disable Nagle on socket, performanec may be poor\n");

LogVerbose("Client connected\n");

//Main command loop
string cmd;
bool query;
string subject;
vector<string> args;
while(true)
{
string line = ScpiRecv(client);
ParseScpiLine(line, subject, cmd, query, args);

//Quit
if(cmd == "EXIT")
break;

//Read ID code
else if( (cmd == "*IDN") && query )
ScpiSend(client, string("Pico Technology,") + g_model + "," + g_serial + "," + g_fwver);

//Unknown
else
{
LogDebug("Unrecognized command received: %s\n", line.c_str());
LogIndenter li;
LogDebug("Subject: %s\n", subject.c_str());
LogDebug("Command: %s\n", cmd.c_str());
if(query)
LogDebug("Query\n");
else
LogDebug("Not query\n");
for(auto arg : args)
LogDebug("Arg: %s\n", arg.c_str());
}
}

LogVerbose("Client disconnected\n");
}
}

void ParseScpiLine(const string& line, string& subject, string& cmd, bool& query, vector<string>& args)
{
//Reset fields
query = false;
subject = "";
cmd = "";
args.clear();

string tmp;
bool reading_cmd = true;
for(size_t i=0; i<line.length(); i++)
{
//If there's no colon in the command, the first block is the command.
//If there is one, the first block is the subject and the second is the command.
if(line[i] == ':')
{
subject = tmp;
tmp = "";
continue;
}

//Detect queries
if(line[i] == '?')
{
query = true;
continue;
}

//Comma delimits arguments, space delimits command-to-args
if(!(isspace(line[i]) && cmd.empty()) && line[i] != ',')
{
tmp += line[i];
continue;
}

//merge multiple delimiters into one delimiter
if(tmp == "")
continue;

//Save command or argument
if(reading_cmd)
cmd = tmp;
else
args.push_back(tmp);

reading_cmd = false;
tmp = "";
}

//Stuff left over at the end? Figure out which field it belongs in
if(tmp != "")
{
if(cmd != "")
args.push_back(tmp);
else
cmd = tmp;
}
}
147 changes: 137 additions & 10 deletions src/ps6000d/main.cpp
Original file line number Diff line number Diff line change
@@ -33,14 +33,7 @@
@brief Program entry point
*/

#ifdef _WIN32
#include <windows.h>
#include <shlwapi.h>
#endif

#include "../../lib/log/log.h"
#include "../../lib/xptools/Socket.h"
#include <thread>
#include "ps6000d.h"

using namespace std;

@@ -52,7 +45,9 @@ void help()
"ps6000d [general options] [logger options]\n"
"\n"
" [general options]:\n"
" --help : this message...\n"
" --help : this message...\n"
" --scpi-port port : specifies the SCPI control plane port (default 5025)\n"
" --waveform-port port : specifies the binary waveform data port (default 5026)\n"
"\n"
" [logger options]:\n"
" levels: ERROR, WARNING, NOTICE, VERBOSE, DEBUG\n"
@@ -67,12 +62,22 @@ void help()
);
}

string g_model;
string g_serial;
string g_fwver;

int16_t g_hScope = 0;

Socket g_scpiSocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);

int main(int argc, char* argv[])
{
//Global settings
Severity console_verbosity = Severity::NOTICE;

//Parse command-line arguments
uint16_t scpi_port = 5025;
uint16_t waveform_port = 5026;
for(int i=1; i<argc; i++)
{
string s(argv[i]);
@@ -86,7 +91,20 @@ int main(int argc, char* argv[])
help();
return 0;
}
else if(s[0] == '-')

else if(s == "--scpi-port")
{
if(i+1 < argc)
scpi_port = atoi(argv[++i]);
}

else if(s == "--waveform-port")
{
if(i+1 < argc)
waveform_port = atoi(argv[++i]);
}

else
{
fprintf(stderr, "Unrecognized command-line argument \"%s\", use --help\n", s.c_str());
return 1;
@@ -96,5 +114,114 @@ int main(int argc, char* argv[])
//Set up logging
g_log_sinks.emplace(g_log_sinks.begin(), new ColoredSTDLogSink(console_verbosity));

//For now, open the first instrument we can find.
//TODO: implement device selection logic
LogNotice("Looking for a PicoScope 6000 series instrument to open...\n");
auto status = ps6000aOpenUnit(&g_hScope, NULL, PICO_DR_8BIT);
if(PICO_OK != status)
{
LogError("Failed to open unit (code %d)\n", status);
return 1;
}

//See what we got
LogNotice("Successfully opened instrument\n");
{
LogIndenter li;

char buf[128];
int16_t required = 0;
status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_DRIVER_VERSION);
if(status == PICO_OK)
LogVerbose("Driver version: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_USB_VERSION);
if(status == PICO_OK)
LogVerbose("USB version: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_HARDWARE_VERSION);
if(status == PICO_OK)
LogVerbose("Hardware version: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_VARIANT_INFO);
if(status == PICO_OK)
{
LogVerbose("Variant info: %s\n", buf);
g_model = buf;
}

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_BATCH_AND_SERIAL);
if(status == PICO_OK)
{
LogVerbose("Batch/serial: %s\n", buf);
g_serial = buf;
}

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_CAL_DATE);
if(status == PICO_OK)
LogVerbose("Cal date: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_KERNEL_VERSION);
if(status == PICO_OK)
LogVerbose("Kernel ver: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_DIGITAL_HARDWARE_VERSION);
if(status == PICO_OK)
LogVerbose("Digital HW ver: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_ANALOGUE_HARDWARE_VERSION);
if(status == PICO_OK)
LogVerbose("Analog HW ver: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_FIRMWARE_VERSION_1);
if(status == PICO_OK)
{
LogVerbose("FW ver 1: %s\n", buf);
g_fwver = buf;
}

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_FIRMWARE_VERSION_2);
if(status == PICO_OK)
LogVerbose("FW ver 2: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_FIRMWARE_VERSION_3);
if(status == PICO_OK)
LogVerbose("FW ver 3: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_FRONT_PANEL_FIRMWARE_VERSION);
if(status == PICO_OK)
LogVerbose("Front panel FW: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_MAC_ADDRESS);
if(status == PICO_OK)
LogVerbose("MAC address: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_DRIVER_PATH);
if(status == PICO_OK)
LogVerbose("Driver path: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_SHADOW_CAL);
if(status == PICO_OK)
LogVerbose("Shadow cal: %s\n", buf);

status = ps6000aGetUnitInfo(g_hScope, (int8_t*)buf, sizeof(buf), &required, PICO_IPP_VERSION);
if(status == PICO_OK)
LogVerbose("IPP version: %s\n", buf);
}

//TODO: Launch the data plane socket server

//Launch the control plane socket server
g_scpiSocket.Bind(scpi_port);
g_scpiSocket.Listen();
thread scpiThread(ScpiServerThread);

//TODO: proper clean shutdown with ^C

//Wait for threads to terminate
scpiThread.join();

//Done
ps6000aCloseUnit(g_hScope);
return 0;
}
Loading