How to load config and save new config LittleFS ESP32? - arduino

I'm using a json file in the data folder. I want to use this as a config file and update it with values as needed. I have a simple sketch to store ssid and password. First read it out, then change it, then read it out again via the serial monitor for testing.
The problem is the first read seems to already have been overwritten, even if i call the overwrite function much later, I have tested this with different delays between the first read and the overwrite function.
It seems pretty straight forward to do, so why does it show the overwritten value before the function is called?
Here is the code:
readJSON(LITTLEFS, "/config.json");
String test = config["ssid"];
Serial.println(test);
delay(10);
String test2;
config["ssid"] = "InternetName";
serializeJson(config, test2);
writeFile(LITTLEFS, "/config.json", test2.c_str()); // String to Const char*
delay(10);
readJSON(LITTLEFS, "/config.json");
String test3 = config["ssid"];
Serial.println(test3);
Here are readJSON and writeFile functions:
DynamicJsonDocument config(1024);
void readJSON(fs::FS &fs, const char * path){
Serial.printf("Reading Json: %s\r\n", path);
String output;
File file = fs.open(path);
if(!file || file.isDirectory()){
Serial.println("- failed to open file for reading");
return;
}
Serial.println("- read from file:");
while(file.available()){
//Serial.write(file.read());
char intRead = file.read();
output += intRead;
}
deserializeJson(config, output);
file.close();
}
void writeFile(fs::FS &fs, const char * path, const char * message){
Serial.printf("Writing file: %s\r\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("- failed to open file for writing");
return;
}
if(file.print(message)){
Serial.println("- file written");
} else {
Serial.println("- write failed");
}
file.close();
}
config.json:
{
"ssid": "nameofinternet",
"password": "password"
}
Output:
Reading Json: /config.json
- read from file:
InternetName
Writing file: /config.json
- file written
Reading Json: /config.json
- read from file:
InternetName

Related

"null" at the end of string from file.readStringUntil()

Im working with esp32s3 feather right now. I need to log some data when there is no WiFi connection. Write works fine for me but when I want to read line with readStringUntil(), i always get "null" at the end of read string. Here is code:
In loop:
if ((millis() - sdLast) > sdTime)
{
for (int i = 0; i < maxSensors; i++)
{
if (activeSensors[i] != "")
{
String requestData = "{\"data\":[{\"name\":\"" + sensorNames[i] + "\" ,\"temp\": \"" + actTemp[i] + "\",\"hum\": \"" + actHum[i] + "\",\"time\": \"" + actTime[i] + "\",\"scanCount\": \"" + scanCount[i] + "\"}]}\n";
appendFile(SD, "/all.txt", requestData.c_str());
sdReady = true;
}
}
sdLast = millis();
}
Function to read from file:
void readLinesSD(fs::FS &fs, const char *path)
{
File file = fs.open(path);
WiFiClient client;
HTTPClient http;
http.begin(client, serverName);
http.addHeader("Content-Type", "application/json");
if (!file)
{
Serial.println("Failed to open file for reading");
return;
}
while (file.available())
{
buffer = file.readStringUntil('\n');
serializeJson(doc, buffer);
Serial.println(buffer);
int httpResponseCode = http.POST(buffer);
Serial.println(httpResponseCode);
doc.clear();
delay(200);
}
http.end();
file.close();
}
Append function:
void appendFile(fs::FS &fs, const char *path, const char *message)
{
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if (!file)
{
Serial.println("Failed to open file for appending");
return;
}
if (file.print(message))
{
Serial.println("Message appended");
}
else
{
Serial.println("Append failed");
}
file.close();
}
SO basically I want to save data to file and then, when the WiFi connection is back I want to send data to database for further presentation. When I read file i got this results:
{"data":[{"name":"P RHT 902631" ,"temp": "19.53","hum": "48","time": "1674746950","scanCount": "4"}]}null
{"data":[{"name":"P RHT 90262A" ,"temp": "19.38","hum": "50","time": "1674746957","scanCount": "4"}]}null
{"data":[{"name":"P RHT 902629" ,"temp": "19.36","hum": "49","time": "1674746958","scanCount": "5"}]}null
I tried using some special characters like "%" at the end of lines and then read line untill this special character but got same problem. When I used the same function on my other esp32 board everything was read fine. Anyone know what might cause this problem? Thanks for any help
I messed up with function to read file. I serialized for no reason. Without it, "null" disapear :)

QBtyeArray QDataStream qCompress to file adding extra leading bytes

Qt/C++ program has a function which writes an objects data (_token) to a file as follows:
QFile f( _tokenFile);
if (!f.open( QIODevice::WriteOnly)) {
qDebug() << "Unable to open token file for writing" << f.errorString();
return false;
}
QByteArray tokenBa;
QDataStream ds( &tokenBa, QIODevice::WriteOnly);
ds << _token;
tokenBa = qCompress( tokenBa);
f.write( tokenBa);
f.close();
The _token is an instance of the following struct:
struct Token {
QString accessToken;
QString refreshToken;
QString clientSecret;
QString authCode;
QTime expiryTime;
enum AuthState {
A_Invalid,
A_RequestAuth,
A_Authenticated,
};
AuthState state;
Token() : state( A_Invalid) {}
bool isValid() const {
if (accessToken.isEmpty() ||
refreshToken.isEmpty()) {
return false;
}
return true;
}
void inValidate() {
accessToken.clear();
refreshToken.clear();
clientSecret.clear();
authCode.clear();
expiryTime = QTime();
}
void cleanUp() {
accessToken.clear();
refreshToken.clear();
clientSecret.clear();
authCode.clear();
expiryTime = QTime();
}
};
When the file is saved it has 4 extra bytes at the start which render the file as a invalid zlib file.
0000000 0000 5e01 9c78 904d 4f5d 5082 871c fa9f
0000020 353e 25cd 6975 2c2f d563 4c2c 62b8 cad1
We can see above bytes 5-6 are 9C 78 which is the zlib signature, but the 4 bytes before these are the issue.
To check the compressed data is correct I do the following:
dd if=file.token bs=1 skip=4 | openssl zlib -d
And this produces the expected result (for testing).
The problem is in the application reading this data back into the data object:
QFile f( _tokenFile);
if (!f.exists()) {
qDebug() << "Token file doesn't exist" << f.fileName();
return false;
}
if (!f.open( QIODevice::ReadOnly)) {
qDebug() << "Unable to open token file for reading" << f.errorString();
return false;
}
QByteArray tokenBa = f.readAll();
f.close();
if (tokenBa.isEmpty()) {
qDebug() << "Token file is empty.";
return false;
}
tokenBa = qUncompress( tokenBa);
QDataStream ds( &tokenBa, QIODevice::ReadOnly);
ds >> _token;
This returns null - because of the leading 4 extraneous bytes. I could put some code in to skip these 4 leading bytes, but how do I know it will always be 4 bytes? I'd like to instead have certainly that the files data is all zlib compressed.
My question is how to avoid those bytes being saved in the first place so that on re-read the format is known to be zlib type?
You can't avoid them since they're needed for qUncompress later on:
Note: If you want to use this function to uncompress external data that was compressed using zlib, you first need to prepend a four byte header to the byte array containing the data. The header must contain the expected length (in bytes) of the uncompressed data, expressed as an unsigned, big-endian, 32-bit integer.

How to use char* with SD library with Arduino?

I am writing a data logger and would like to keep the files limited to a specific number of entries. I am trying to write this bit of code in the setup, so that when the Arduino powers on, it will write to a new file just to keep things simple. However, when I try to open the file I can't, although I am not sure why. Can anyone offer any explanation?
char *fileName; //global name
File logFile; //global file
//everything else is in setup()
char * topPart = "/Data/Data"; //first part of every file
char * lowerPart = ".txt"; // jus the extention
char * itter; //used to hold the char of i later
fileName = "/Data/Data.txt"; //start with the first file possible.
for(int i=0; i<=100;i++) {
if(!SD.exists(fileName)) {
Serial.print("opening file: ");
Serial.print(fileName);
logFile = SD.open(fileName, FILE_WRITE);
if(logFile) {
logFile.println("I made it");
Serial.println("in the file");
}
if(!logFile) {
Serial.println("somthing bad");
}
break;
} else {
itter = (char *)(i+48);
strcpy(fileName,topPart);
strcpy(fileName,itter);
strcpy(fileName,lowerPart);
Serial.println(i);
}
}
Lots of problems.
the construction of itter is wrong.
strcpy doesn't append just cpy.
Here is a code example to build your filename. This a basic C program. Remove the #include and main for Arduino, this allows to test on your computer whether the program is ok.
#include <string.h>
#define TOPPART "/Data/Data"
#define LOWERPART ".txt"
int main(void) {
char buf[64];
snprintf(buf, sizeof(buf), "%s%s", TOPPART, LOWERPART);
for (int i = 0; i < 100; i++) {
/* here your stuff to check if the filename froml buf exists*/
snprintf(buf, sizeof(buf), "%s%d%s", TOPPART, i, LOWERPART);
}
return 0;
}

Arduino error: does not name a type?

I have written a library, but have problem with error does not name a type. I've tryed everything, searched for couple of hours and no luck. Library is placed in the "libraries" folder of the arduino sketch folder. Please help!!! I am using OSX, but the same problem occurs on Windows also.
This is header file of the library:
#ifndef OpticalSensor_h
#define OpticalSensor_h
#include <Arduino.h>
#include <SD.h>
#include <Wire.h>
#include <Adafruit_MCP23017.h>
#include <Adafruit_RGBLCDShield.h>
#include <String.h>
class OpticalSensor
{
public:
OpticalSensor(int analogPort);
void LCDInit(int columns, int rows);
void SerialInit(int bitRate);
void SDInit();
double& ReadFromAnalogPort();
void SDCreateFile(String fileName);
void SDDeleteFile(String fileName);
void SDWriteToFile(String fileName);
void SDStreamToFile(String Text);
void SDOpenFileToStream(String fileName);
private:
int _analogPort;
bool _displayFlag;
Adafruit_RGBLCDShield _lcd;
File _MainRecFile;
double _voltage;
void _LCDClearAll();
void _LCDWriteInTwoRows(String row1, String row2);
void _DelayAndClearLCD(bool returnStatus);
};
#endif
This is .cpp file of the library:
#include <OpticalSensor.h>
Adafruit_RGBLCDShield _lcd;
File _MainRecFile;
double _voltage;
OpticalSensor::OpticalSensor(int analogPort)
{
_analogPort = analogPort;
}
void OpticalSensor::LCDInit(int columns, int rows)
{
_lcd = Adafruit_RGBLCDShield();
_lcd.begin(columns,rows);
}
void OpticalSensor::SerialInit(int bitRate)
{
Serial.begin(bitRate);
_bitRate = bitRate;
while(!Serial) {
//wait until serial is not open
}
}
void OpticalSensor::SDInit()
{
// On the Ethernet Shield, CS is pin 4. It's set as an output by default.
// Note that even if it's not used as the CS pin, the hardware SS pin
// (10 on most Arduino boards, 53 on the Mega) must be left as an output
// or the SD library functions will not work.
pinMode(10, OUTPUT);
//check if SD can be found and initialized. Print also message to
//Serial if initialized and to _lcd if initialized.
if(!SD.begin(4)) {
if(Serial){
Serial.println("Initialization failed!");
}
if(_lcd){
_lcd.print("Init failed!");
}
_DelayAndClearLCD(true);
}
else {
if(Serial) {
Serial.println("Initialization done!");
}
if(_lcd) {
lcd.print("Init done!");
}
_DelayAndClearLCD(false);
}
}
void OpticalSensor::SDCreateFile(String fileName)
{
//check if file allready exists, if not it creates one
//and writes apropriate response to
//lcd and Serial if they are initialized.
if(SD.exists(fileName)) {
if(Serial) {
Serial.println(fileName + " already exists!");
}
if(_lcd) {
_LCDWriteInTwoLines(fileName,"already exists!");
}
_DelayAndClearLCD(false);
}
else
{
if(Serial) {
Serial.println(fileName + "Creating file " + fileName + "...");
}
if(_lcd) {
_LCDWriteInTwoLines("Creating file", fileName);
}
_MainRecFile = SD.open(fileName + ".txt", FILE_WRITE);
_MainRecFile.close();
_DelayAndClearLCD(false);
//check if file was created successffully and print apropriate response
//to lcd and Serial if they are initialized
if(SD.exists(fileName + ".txt")) {
if(Serial) {
Serial.println(fileName + ".txt" + " created successffully!");
}
if(_lcd) {
_LCDWriteInTwoLines(fileName + ".txt", "created!");
}
_DelayAndClearLCD(false);
}
else {
if(Serial) {
Serial.println("error: failed to create file!");
}
if(_lcd) {
_LCDWriteInTwoLines("error: failed to","create file!");
}
_DelayAndClearLCD(false);
}
}
}
//delete file from SD card
void OpticalSensor::SDDeleteFile(String fileName)
{
}
//open file, write data to it, and close file after.
void OpticalSensor::SDWriteToFile(String fileName, String Text)
{
_MainRecFile = SD.open(fileName + ".txt", FILE_WRITE);
_MainRecFile.println(Text);
_MainRecFile.close();
}
//Open file to stream data to it.
void OpticalSensor::SDOpenFileToStream(String fileName)
{
_MainRecFile = SD.open(fileName + ".txt", FILE_WRITE);
}
//Write data to file while file is open.
//Notice that you can stream data only to one file at a time!!!
//For instance, if you have two sensors that you want to
//write data to two different files, you have to use SDWriteToFile
//function!!!
void OpticalSensor::SDStreamToFile(String Text)
{
if(_MainRecFile) {
_MainRecFile.println(Text);
}
}
//close file that you streamed data too.
void OpticalSensor::SDCloseStreaming(String fileName)
{
_MainRecFile.close();
}
//clear entire LCD
void OpticalSensor::_LCDClearAll()
{
_lcd.clear();
_lcd.setCursor(0,0);
}
void OpticalSensor::_LCDWriteInTwoRows(String row1, String row2)
{
//write first String in row1
_lcd.print(row1);
//set cursor to the beginning of row 2
_lcd.setCursor(0,1);
//write second String to row 2
_lcd.print(row2);
}
void OpticalSensor::_DelayAndClearLCD(bool returnStatus)
{
//if Serial or _lcd are initialized, delay for 2 seconds
//and clear LCD
if(Serial || _lcd) {
delay(2000);
if(_lcd)
_LCDClearAll();
}
//terminate
if(bool == true) {
return;
}
}
double& ReadFromAnalogPort()
{
_voltage = analogRead(_analogPort);
return _voltage;
}
And this is the .ino file where library is included:
#include <OpticalSensor.h>
OpticalSensor sensor(0);
void setup() {
sensor.LCDInit(16,2);
sensor.SerialInit(9600);
sensor.SDInit();
sensor.SDCreateFile("test1");
sensor.SDOpenFileToStream("test1");
}
void loop() {
}
this is the error:
In file included from Test_OpticalSensorLib.ino:1:
/Users/gaspersladic/Documents/Arduino/libraries/OpticalSensor/OpticalSensor.h:34:
error: 'Adafruit_RGBLCDShield' does not name a type
/Users/gaspersladic/Documents/Arduino/libraries/OpticalSensor/OpticalSensor.h:35:
error: 'File' does not name a type
The two includes you mention in your comment are essential. 'does not name a type' just means there is no definition for that identifier visible to the compiler. If there are errors in the LCD library you mention, then those need to be addressed - omitting the #include will definitely not fix it!
Two notes from experience which might be helpful:
You need to add all #include's to the main sketch - irrespective of whether they are included via another #include.
If you add files to the library folder, the Arduino IDE must be restarted before those new files will be visible.
I got the does not name a type error when installing the NeoMatrix library.
Solution: the .cpp and .h files need to be in the top folder when you copy it, e.g:
myArduinoFolder/libraries/Adafruit_NeoMatrix/Adafruit_NeoMatrix.cpp
When I used the default Windows unzip program, it nested the contents inside another folder:
myArduinoFolder/libraries/Adafruit_NeoMatrix/Adafruit_NeoMatrix/Adafruit_NeoMatrix.cpp
I moved the files up, so it was:
myArduinoFolder/libraries/Adafruit_NeoMatrix/Adafruit_NeoMatrix.cpp
This fixed the does not name a type problem.
I found the solution to this problem in a "}". I did some changes to my sketch and forgot to check for "}" and I had an extra one. As soon as I deleted it and compiled everything was fine.
Don't know it this is your problem but it was mine.
Void setup() does not name a type
BUT
void setup() is ok.
I found that the sketch I copied for another project was full of 'wrong case' letters. Onc efixed, it ran smoothly.emphasized text
The only thing you have to do is adding this line to
your sketch
#include <SPI.h>
before #include <Adafruit_MAX31855.h>.
More recently, I have found that other factors will also cause this error. I had an AES.h, AES.cpp containing a AES class and it gave this same unhelpful error. Only when I renamed to Encryption.h, Encryption.cpp and Encryption as the class name did it suddenly start working. There were no other code changes.
My code was out of void setup() or void loop() in Arduino.
Usually Header file syntax start with capital letter.I found that code written all in smaller letter
#ifndef DIAG_H
#define DIAG_H
#endif

How to easily establish an SSH connection in Qt?

I'm looking for an easy way to establish an ssh connection in Qt similar to how I can operate in Java.
For instance, to log in to an ssh connection via java, I can do:
import com.sshtools.j2ssh.SshClient;
import com.sshtools.j2ssh.transport.IgnoreHostKeyVerification;
import com.sshtools.j2ssh.authentication.PasswordAuthenticationClient;
import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
......
SshClient ssh = new SshClient();
try {
// Timeout of ten seconds
ssh.setSocketTimeout(10*1000);
ssh.connect(ip, port, new IgnoreHostKeyVerification());
PasswordAuthenticationClient auth = new PasswordAuthenticationClient();
auth.setUsername(username);
auth.setPassword(password);
if (ssh.authenticate(auth) != AuthenticationProtocolState.COMPLETE) {
errorLabel.setForeground(Color.RED);
errorLabel.setText("Username or password is incorrect");
}
else
successful = true;
}
catch (Exception e) {
errorLabel.setForeground(Color.RED);
errorLabel.setText("Cannot log into website");
e.printStackTrace();
}
The solutions I've seen for Qt are:
Roll my own using libssh
Roll my own using libssh2
Use QProcess to call an outside SSH process to establish a connection
Buy the library here: http://netsieben.com/products/ssh/
Since the Java version is free, I'm a bit loathe to spend money on this, but if I have to, I have to. I'd rather not roll my own, because I can only see that being a cause of mistakes (unless those libraries are easy to use?).
Any thing I haven't found?
EDIT: I have now spent days wrestling with both libssh and libqxt. Both are utter failures on windows, from what I can determine. Libqxt won't compile on windows with ssh, even after following the steps described here and here. Libssh's C++ wrapper class includes everything in the header, which is causing linking failures if not carefully shepherded. Once those linker issues are solved, Libssh's compiled library crashes CDB, so debugging with libssh becomes impossible, regardless of whether or not the c++ wrapper is used. So now I'm back to libssh2 or paying for it via some dubious program that has apparently not been updated in four years. Cryptlib, maybe? It doesn't help that most of the qt forum help seems to be 'spawn another process', which therefore requires my users to have ssh installed, which is definitely not par for the course on Windows.
Here is an asynchronous ssh & scp "socket" I wrote for Qt that is cross platform. This requires libSSH and is not perfect (hides rsa key passing stuff, delivers command responses in single shot instead of via readyRead signals) , but it will most likely work for your needs. In order to get this working for windows this file must be the FIRST file included in your project (so that qt doesn't try and import windows.h before this can import the proper windows socket code).
Header:
#ifndef QSSHSOCKET_H
#define QSSHSOCKET_H
#include <libssh/libssh.h>
#include <QByteArray>
#include <QThread>
#include <QVector>
#include <QFile>
#include <sys/stat.h>
#include <sys/types.h>
#include <QDebug>
class QSshSocket: public QThread
{
Q_OBJECT
public:
enum SshError
{
/*! \brief There was trouble creating a socket. This was most likely due to the lack of an internet connection.*/
SocketError,
/*! \brief The ssh session could not be created due to inability to find the remote host.*/
SessionCreationError,
/*! \brief An ssh channel could not be created for the previous operation.*/
ChannelCreationError,
/*! \brief An scp channel could not be created for the previous file transfer operation.*/
ScpChannelCreationError,
/*! \brief There was an error requesting a pull file transfer.*/
ScpPullRequestError,
/*! \brief There was an error requesting a push file transfer.*/
ScpPushRequestError,
/*! \brief The destination file for the previous transfer does not exist.*/
ScpFileNotCreatedError,
/*! \brief There was an error reading a remote file. This could possibly be due to user permissions.*/
ScpReadError,
/*! \brief There was an error writing to a remote file. This could possibly be due to user permissions.*/
ScpWriteError,
/*! \brief The credentials of a user on the remote host could not be authenticated.*/
PasswordAuthenticationFailedError
};
/*!
\param position The center position of the box.
\param size The size of the box.
\brief The constructor.
*/
explicit QSshSocket(QObject * parent = 0);
/*!
\brief The deconstructor.
*/
~QSshSocket();
/*!
\param host The hostname to establish an ssh connection with.
\param port The port to establish an ssh connection over.
\brief This function connects this socket to the specified host over the specified port. On success, the signal connected is emitted while error is emmited on failure.
*/
void connectToHost(QString host, int port =22);
/*!
\brief This function disconnects the socket from the current host (if there is one. On success, the signal disconnected is emitted while error is emmited on failure.
*/
void disconnectFromHost();
/*!
\param command The command to be executed.
\brief This function executes a remote command on the connected host. If not connected to a remote host, the command is not executed.
On success, the signal commandExecuted is emitted while error is emmited on failure.
*/
void executeCommand(QString command);
/*!
\brief Returns the hostname of the remote machine this socket is connected to. If not connected to a remote host, this returns "".
*/
QString host();
/*!
\brief Returns whether or not a user has been logged in at remote host.
*/
bool isLoggedIn();
/*!
\brief Returns whether or not this socket is currently connected to a remote host.
*/
bool isConnected();
/*!
\param user The username to login with.
\param password The password of the account for the specified username.
\brief This function to login to the currently connected host given credentials.
On success, the signal authenticated is emitted while error is emmited on failure.
*/
void login(QString user, QString password);
/*!
\brief Returns the port of the current connection. If not connected to a remote host, this returns -1.
*/
int port();
/*!
\param localPath A path to a file stored on the local machine.
\param password A path to a file stored on the remote machine.
\brief This function attempts to pull a remote file from the connected host to a local file. The local file does not need to be created beforehand.
On success, the signal pullSuccessful is emitted while error is emmited on failure.
If not connected to a remote host, or if the transfer was unsuccessful, the signal error is emitted.
*/
void pullFile(QString localPath, QString remotePath);
/*!
\param localPath A path to a file stored on the local machine.
\param password A path to a file stored on the remote machine.
\brief This function attempts to pull a remote file from the connected host to a local file. The local file does not need to be created beforehand.
On success, the signal pushSuccessful is emitted while error is emmited on failure.
If not connected to a remote host, or if the transfer was unsuccessful, the signal error is emitted.
*/
void pushFile(QString localPath, QString remotePath);
/*!
\param path A relative or absolute path to a directory on the remote host.
\brief This function attempts to set the working directory of the connection to path and emits workingDirectorySet upon completion.
If workingDirectorySet indicates no change in the working directory, the path could not be found.
If not connected to a remote host the signal error will be emitted.
*/
void setWorkingDirectory(QString path);
/*!
\brief Returns the username of the current authenticated user on the remote host. If not connected to a remote host, or if a user has not been authenticated this returns "".
*/
QString user();
signals:
/*!
\brief This signal is emitted when remote host has been connected to."
*/
void connected();
/*!
\brief This signal is emitted when this class has been properly disconnected from a remote host.
*/
void disconnected();
/*!
\param error The type of error that occured.
\brief This signal is emitted when an error occurs.
*/
void error(QSshSocket::SshError error);
/*!
\param command The command that was executed on the remote host.
\param response The response to the command that was executed.
\brief This signal is emitted when a response from the remote host is received regarding a command.
*/
void commandExecuted(QString command,QString response);
/*!
\brief This signal is emitted when a user has been loggen in to the remote host."
*/
void loginSuccessful();
/*!
\param localFile The path to a local file that the remote file was pulled to.
\param remoteFile The path to a file pulled from the remote host.
\brief This signal is emitted when a remote file is successfully transfered to a local file.
*/
void pullSuccessful(QString localFile, QString remoteFile);
/*!
\param localFile The path to a local file pushed to the remote host.
\param remoteFile The path to a remote file that the local file was pushed to.
\brief This signal is emitted when a local file is successfully transfered to a remote file.
*/
void pushSuccessful(QString localFile, QString remoteFile);
/*!
\param cwd The current working directory of the session on the remote host.
\brief This signal is emitted when a current working directory is set.
*/
void workingDirectorySet(QString cwd);
private slots:
void run();
private:
enum SSHOperationType
{
Command,
WorkingDirectoryTest,
Pull,
Push
};
struct SSHOperation
{
SSHOperationType type;
QString adminCommand,command, localPath, remotePath;
bool executed;
};
int m_port;
bool m_loggedIn ;
QThread * m_thread;
QString m_workingDirectory,m_nextWorkingDir,m_user, m_host,m_password;
SSHOperation m_currentOperation;
ssh_session m_session;
bool m_connected,m_run;
};
#endif // QSSHSOCKET_H
Source:
#include "qsshsocket.h"
#include <QFileInfo>
// if compiling in windows, add needed flags.
#ifdef _WIN32
# include <io.h>
typedef int mode_t;
/// #Note If STRICT_UGO_PERMISSIONS is not defined, then setting Read for any
/// of User, Group, or Other will set Read for User and setting Write
/// will set Write for User. Otherwise, Read and Write for Group and
/// Other are ignored.
///
/// #Note For the POSIX modes that do not have a Windows equivalent, the modes
/// defined here use the POSIX values left shifted 16 bits.
static const mode_t S_ISUID = 0x08000000; ///< does nothing
static const mode_t S_ISGID = 0x04000000; ///< does nothing
static const mode_t S_ISVTX = 0x02000000; ///< does nothing
static const mode_t S_IRUSR = mode_t(_S_IREAD); ///< read by user
static const mode_t S_IWUSR = mode_t(_S_IWRITE); ///< write by user
static const mode_t S_IXUSR = 0x00400000; ///< does nothing
# ifndef STRICT_UGO_PERMISSIONS
static const mode_t S_IRGRP = mode_t(_S_IREAD); ///< read by *USER*
static const mode_t S_IWGRP = mode_t(_S_IWRITE); ///< write by *USER*
static const mode_t S_IXGRP = 0x00080000; ///< does nothing
static const mode_t S_IROTH = mode_t(_S_IREAD); ///< read by *USER*
static const mode_t S_IWOTH = mode_t(_S_IWRITE); ///< write by *USER*
static const mode_t S_IXOTH = 0x00010000; ///< does nothing
# else
static const mode_t S_IRGRP = 0x00200000; ///< does nothing
static const mode_t S_IWGRP = 0x00100000; ///< does nothing
static const mode_t S_IXGRP = 0x00080000; ///< does nothing
static const mode_t S_IROTH = 0x00040000; ///< does nothing
static const mode_t S_IWOTH = 0x00020000; ///< does nothing
static const mode_t S_IXOTH = 0x00010000; ///< does nothing
# endif
static const mode_t MS_MODE_MASK = 0x0000ffff; ///< low word
#endif
QSshSocket::QSshSocket(QObject * parent )
:QThread(parent)
{
m_host = "";
m_user = "";
m_password = "";
m_port = -1;
m_loggedIn = false;
m_session = NULL;
m_workingDirectory = ".";
qRegisterMetaType<QSshSocket::SshError>("QSshSocket::SshError");
m_currentOperation.executed = true;
m_run = true;
start();
}
QSshSocket::~QSshSocket()
{
m_run = false;
this->wait();
}
void QSshSocket::run()
{
while(m_run)
{
if (m_session == NULL)
{
if (m_host != "")
{
m_session = ssh_new();
//set logging to verbose so all errors can be debugged if crash happens
int verbosity = SSH_LOG_PROTOCOL;
// set the pertinant ssh session options
ssh_options_set(m_session, SSH_OPTIONS_HOST, m_host.toAscii().data());
ssh_options_set(m_session, SSH_OPTIONS_USER, m_user.toAscii().data());
ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
ssh_options_set(m_session, SSH_OPTIONS_PORT, &m_port);
// try to connect given host, user, port
int connectionResponse = ssh_connect(m_session);
// if connection is Successful keep track of connection info.
if (connectionResponse == SSH_OK)
connected();
else
error(SessionCreationError);
}
}
// if we have a vaild ssh connection, authenticate connection with credentials
else if (!m_loggedIn)
{
// check to see if a username and a password have been given
if (m_user != "" && m_password !="")
{
// try authenticating current user at remote host
int worked = ssh_userauth_password(m_session, m_user.toAscii().data(), m_password.toAscii().data());
// if successful, store user password.
if (worked == SSH_OK)
{
loginSuccessful();
m_loggedIn = true;
}
else
{
m_user = "";
m_password = "";
error(PasswordAuthenticationFailedError);
}
}
}
// if all ssh setup has been completed, check to see if we have any commands to execute
else if (!m_currentOperation.executed)
{
if (m_currentOperation.type == Command || m_currentOperation.type == WorkingDirectoryTest)
{
// attempt to open ssh shell channel
ssh_channel channel = ssh_channel_new(m_session);
// if attempt fails,return
if (ssh_channel_open_session(channel) != SSH_OK)
{
error(ChannelCreationError);
}
int requestResponse = SSH_AGAIN;
// attempt to execute shell command
while (requestResponse == SSH_AGAIN)
requestResponse = ssh_channel_request_exec(channel, m_currentOperation.adminCommand.toAscii().data());
// if attempt not executed, close connection then return
if (requestResponse != SSH_OK)
{
error(ChannelCreationError);
}
QByteArray buffer;
buffer.resize(1000);
// read in command result
int totalBytes = 0, newBytes = 0;
do
{
newBytes = ssh_channel_read(channel, &(buffer.data()[totalBytes]), buffer.size() - totalBytes, 0);
if (newBytes > 0)
totalBytes += newBytes;
}while (newBytes > 0);
// close channel
ssh_channel_send_eof(channel);
ssh_channel_close(channel);
ssh_channel_free(channel);
QString response = QString(buffer).mid(0,totalBytes);
response.replace("\n","");
if (m_currentOperation.type == WorkingDirectoryTest)
{
if (response == "exists")
m_workingDirectory = m_nextWorkingDir;
m_nextWorkingDir = ".";
workingDirectorySet(m_workingDirectory);
}
else
commandExecuted( m_currentOperation.command, response) ;
}
// if all ssh setup has been completed, check to see if we have any file transfers to execute
else if (m_currentOperation.type == Pull)
{
ssh_scp scpSession = ssh_scp_new(m_session,SSH_SCP_READ, m_currentOperation.remotePath.toAscii().data());
if (scpSession == NULL)
error(ScpChannelCreationError);
// attempt to initialize new scp session.
int scpInitialized = ssh_scp_init(scpSession);
if(scpInitialized != SSH_OK)
{
ssh_scp_close(scpSession);
ssh_scp_free(scpSession);
error(ScpChannelCreationError);
}
// attempt to authorize new scp pull
if (ssh_scp_pull_request(scpSession) != SSH_SCP_REQUEST_NEWFILE)
{
ssh_scp_close(scpSession);
ssh_scp_free(scpSession);
error(ScpPullRequestError);
}
// accept authorization
ssh_scp_accept_request(scpSession);
// get remote file size
int size = ssh_scp_request_get_size(scpSession);
// resize buffer, read remote file into buffer
QByteArray buffer;
buffer.resize(size);
// if an error happens while reading, close the scp session and return
if (ssh_scp_read(scpSession, buffer.data() , size) == SSH_ERROR)
{
ssh_scp_close(scpSession);
ssh_scp_free(scpSession);
error(ScpReadError);
}
// loop until eof flag
if (ssh_scp_pull_request(scpSession) != SSH_SCP_REQUEST_EOF)
{
ssh_scp_close(scpSession);
ssh_scp_free(scpSession);
error(ScpReadError);
}
//close scp session
ssh_scp_close(scpSession);
ssh_scp_free(scpSession);
// open up local file and write contents of buffer to it.
QFile file(m_currentOperation.localPath);
file.open(QIODevice::WriteOnly);
file.write(buffer);
file.close();
pullSuccessful(m_currentOperation.localPath,m_currentOperation.remotePath);
}
else if (m_currentOperation.type == Push)
{
// attempt to create new scp from ssh session.
ssh_scp scpSession = ssh_scp_new(m_session,SSH_SCP_WRITE, m_currentOperation.remotePath.toAscii().data());
// if creation failed, return
if (scpSession == NULL)
error(SocketError);
// attempt to initialize new scp session.
int scpInitialized = ssh_scp_init(scpSession);
// if failed, close scp session and return.
if(scpInitialized != SSH_OK)
{
ssh_scp_close(scpSession);
ssh_scp_free(scpSession);
error(ScpChannelCreationError);
}
// open the local file and check to make sure it exists
// if not, close scp session and return.
QFile file(m_currentOperation.localPath);
if (!file.exists())
{
ssh_scp_close(scpSession);
ssh_scp_free(scpSession);
error(ScpFileNotCreatedError);
}
// if the file does exist, read all contents as bytes
file.open(QIODevice::ReadOnly);
QByteArray buffer =file.readAll();
file.close();
// attempt to authorize pushing bytes over scp socket
// if this fails, close scp session and return.
if (ssh_scp_push_file(scpSession, m_currentOperation.remotePath.toAscii().data(), buffer.size(), S_IRUSR | S_IWUSR) != SSH_OK)
{
ssh_scp_close(scpSession);
ssh_scp_free(scpSession);
error(ScpPushRequestError);
}
// once authorized to push bytes over scp socket, start writing
// if an error is returned, close scp session and return.
if ( ssh_scp_write(scpSession,buffer.data(), buffer.size()) != SSH_OK)
{
ssh_scp_close(scpSession);
ssh_scp_free(scpSession);
error(ScpWriteError);
}
// close scp session and return.
ssh_scp_close(scpSession);
ssh_scp_free(scpSession);
pushSuccessful(m_currentOperation.localPath,m_currentOperation.remotePath);
}
m_currentOperation.executed = true;
}
else
{
msleep(100);
}
}
}
void QSshSocket::disconnectFromHost()
{
m_host = "";
m_user = "";
m_password = "";
m_port = -1;
m_loggedIn = false;
if (m_session != NULL)
{
ssh_disconnect(m_session);
ssh_free(m_session);
}
m_session = NULL;
}
void QSshSocket::connectToHost(QString host, int port)
{
m_host = host;
m_port = port;
}
void QSshSocket::login(QString user, QString password)
{
m_user = user;
m_password = password;
}
void QSshSocket::executeCommand(QString command)
{
m_currentOperation.type = Command;
if (m_workingDirectory != ".")
m_currentOperation.adminCommand = "cd " + m_workingDirectory + "; " + command;
else
m_currentOperation.adminCommand = command ;
m_currentOperation.command =command;
m_currentOperation.executed = false;
}
void QSshSocket::pullFile(QString localPath, QString remotePath)
{
m_currentOperation.localPath = localPath;
if (QFileInfo(remotePath).isAbsolute())
m_currentOperation.remotePath = remotePath;
else
m_currentOperation.remotePath = m_workingDirectory + "/" + remotePath;
m_currentOperation.type = Pull;
m_currentOperation.executed = false;
}
void QSshSocket::pushFile(QString localPath, QString remotePath)
{
m_currentOperation.localPath = localPath;
if (QFileInfo(remotePath).isAbsolute())
m_currentOperation.remotePath = remotePath;
else
m_currentOperation.remotePath = m_workingDirectory + "/" + remotePath;
m_currentOperation.type = Push;
m_currentOperation.executed = false;
}
void QSshSocket::setWorkingDirectory(QString path)
{
m_nextWorkingDir = path;
m_currentOperation.type = WorkingDirectoryTest;
m_currentOperation.adminCommand = "[ -d " + m_nextWorkingDir +" ] && echo 'exists'";
m_currentOperation.executed = false;
}
bool QSshSocket::isConnected()
{
return m_session != NULL;
}
bool QSshSocket::isLoggedIn()
{
return m_loggedIn;
}
QString QSshSocket::user(){return m_user;}
QString QSshSocket::host(){return m_host;}
int QSshSocket::port(){return m_port;}
I searched for some sort of solution to this for a couple days and then forgot about the problem. Then today I stumbled across this little gem in the Qt-Creator source Utils::ssh, includes support for SFTP, plain-old SSH, and all sorts of goodies.
Disentangling stuff from Qt-Creator can be a pain, but having gone through this process it amounts to grabbing Botan (one of the other libs in QT-Creator) + Utils
See LibQxt. Look for QxtSsh classes. hg repo using libssh2 internally.
I'm using libssh2 http://www.libssh2.org/ with qt. I'm using it in the easiest way - native sockets. I had no time to convert it to use qt sockets. However it works great.
The project description is:
http://www.chaosstuff.com/2013/09/gnome-mplayer-remote-with-qt-and-libssh2.html
and the source code is in:
https://bitbucket.org/nchokoev/qtsshremote

Resources