summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp798
1 files changed, 798 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..d986f91
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,798 @@
+#include <chrono>
+#include <errno.h>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <string.h>
+#include <sys/mman.h>
+#include <thread>
+
+#include "LibCamera.h"
+#include "calibration.h"
+#include "camera/innomakerov9281.h"
+#include "camera/ov9281.h"
+#include "dumps.h"
+#include "fuck_intel.h"
+#include "genetic_algos.h"
+#include "httpservice.h"
+#include "imagealgos.h"
+#include "laser.h"
+#include "macro.h"
+#include "pigpio.h"
+#include "printerclient.h"
+#include "profile.h"
+#include "rotaryencoder.h"
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QHttpServer>
+#include <QImage>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QSerialPort>
+#include <QTextStream>
+#include <QTimer>
+#include <QtConcurrent/QtConcurrent>
+
+#define try_apply_config() \
+ if (!applyConfig(config)) \
+ { \
+ camera->release(); \
+ cm->stop(); \
+\
+ return EXIT_FAILURE; \
+ }
+
+ScanningModeFlags scanningModeFlags{ScanningModeFlags::None};
+
+QElapsedTimer calibrationTimer;
+
+extern volatile int32_t positionSteps;
+
+requested_params_t requested_params;
+
+namespace {
+// std::shared_ptr<Image> img;
+Image *img = nullptr;
+Pixels pixels;
+std::vector<Pixels> calibrationPixels;
+QMutex calibrationPixelsMutex;
+} // namespace
+
+using namespace std::chrono_literals;
+
+// static std::shared_ptr<libcamera::Camera> camera;
+// std::unique_ptr<libcamera::CameraConfiguration> config;
+// static std::map<int, std::pair<void*, unsigned int>> mappedBuffers_;
+// std::vector<std::unique_ptr<libcamera::Request>> requests;
+libcamera::ControlList lastControls;
+
+namespace {
+CalibrationTablePtr calibrationTableZ;
+CalibrationTablePtr calibrationTableX;
+} // namespace
+
+// static bool applyConfig(
+// const std::unique_ptr<libcamera::CameraConfiguration>& config
+// );
+// static void onRequestCompleted(libcamera::Request* completed_request);
+// static void printControls();
+// static QList<Pixels> filter(const QList<Pixels>& rawProfiles);
+
+auto printPixels = [](const auto& pixels) {
+ for (size_t i = (img_width - 10) / 2;
+ i < img_width - ((img_width - 10) / 2);
+ ++i)
+ {
+ std::cout << pixels[i] << " ";
+ }
+ std::cout << std::endl;
+};
+
+// void onNewImage(std::shared_ptr<Image> image)
+void onNewImage(Image &image)
+{
+ // std::cout << __func__ << std::endl << std::flush;
+
+ // if (!image)
+ // {
+ // qDebug() << __func__ << "no image";
+ // return;
+ // }
+
+ ::img = &image;
+}
+
+void onNewPixels(std::shared_ptr<Pixels> pixels)
+{
+ // std::cout << __func__ << std::endl << std::flush;
+
+ if (!pixels)
+ {
+ qDebug() << __func__ << "got null pixels";
+ }
+
+ if (!*pixels)
+ {
+ // qDebug() << __func__ << "got empty pixels";
+ }
+
+ for (size_t i = 640 - 5; i < 640 + 5; i++)
+ {
+ // std::cout << pixels->pixels[i] << " ";
+ }
+
+ // std::cout << std::endl
+
+ ::pixels = *pixels;
+}
+
+bool initLaser();
+
+int main(int argc, char* argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ QList<QFuture<void>> initializers;
+
+#ifdef INNO_MAKER
+ if (false)
+ {
+ std::cout << std::boolalpha;
+ InnoMakerOV9281 innoMakerCam;
+ qDebug() << "init:" << innoMakerCam.init();
+ qDebug() << "set exposure:" << innoMakerCam.setExposureTimeUs(3000000);
+ qDebug() << "set gain:" << innoMakerCam.setGain(2);
+
+ innoMakerCam.startStream();
+ QThread::sleep(3);
+ qDebug() << "should be stopped";
+ // Image buf;
+
+ // for (size_t i = 0; i < 1000; ++i) {
+ // if (!innoMakerCam.getImage(buf)) {
+ // break;
+ // }
+
+ // buf.rotate();
+ // auto pixels = buf.pixels();
+ // }
+ }
+ // qDebug() << "ok";
+ // exit(EXIT_SUCCESS);
+#endif
+
+ // if (false)
+ qDebug() << "size of raw profile" << sizeof(Pixels);
+ if (true)
+ {
+ // open binary calibration table
+ if (true)
+ {
+ initializers << QtConcurrent::run([&]() {
+ if (!openCalibrationTable(
+ // "/home/user/dumps/binz.calibration_table",
+ "/tmp/binz.calibration_table",
+ ::calibrationTableZ)) {
+ exit(EXIT_FAILURE);
+ }
+ });
+
+ initializers << QtConcurrent::run([&]() {
+ if (!openCalibrationTable(
+ // "/home/user/dumps/binx.calibration_table",
+ "/tmp/binx.calibration_table",
+ ::calibrationTableX)) {
+ exit(EXIT_FAILURE);
+ }
+ });
+ }
+
+ if (false)
+ {
+ // z
+ // if (!openCalibrationTable(
+ // "/home/user/dumps/binz.calibration_table",
+ // ::calibrationTableZ
+ // ))
+ // {
+ // exit(EXIT_FAILURE);
+ // }
+
+ // if (!calibrationTableToImage(::calibrationTableZ)
+ // .save("/home/user/dumps/imageZ.png"))
+ // {
+ // qDebug() << "cannot save imageZ.png";
+ // exit(EXIT_FAILURE);
+ // }
+
+ // interpolate(::calibrationTableZ);
+ // exit(EXIT_SUCCESS);
+
+ // calibrationTableToImage(::calibrationTableZ)
+ // .save("/home/user/dumps/imageZ_interpolated.png");
+
+ auto rawProfiles = openDump("/home/user/dumps/binx");
+ qDebug() << "raw x-profiles count is" << rawProfiles.size();
+ // qDebug() << "height" << calibrationColumnHeight;
+
+ auto filteredRawProfiles = filter(std::move(rawProfiles));
+ qDebug() << "filtered x-profiles count is"
+ << filteredRawProfiles.count();
+
+ ::calibrationTableX = calibrateX(std::move(filteredRawProfiles));
+
+ // for (size_t i = 9471; i < 9472; i++) {
+ // std::cout << "row #" << i << ": ";
+
+ // for (size_t j = 0; j < 1280; ++j) {
+ // const auto& p = ::calibrationTableX->at(j).at(i);
+ // std::cout << p << ' ';
+ // }
+
+ // std::cout << std::endl;
+ // }
+
+ // x
+ // qDebug() << "open x table";
+ // if (!openCalibrationTable("/home/user/dumps/binx.calibration_table",
+ // ::calibrationTableX)) {
+ // exit(EXIT_FAILURE);
+ // }
+
+ // if (!calibrationTableToImage(::calibrationTableX)
+ // .save("/home/user/dumps/imageX.png")) {
+ // qDebug() << "cannot save imageX.png";
+ // exit(EXIT_FAILURE);
+ // }
+
+ // for (size_t i = 9471; i < 9472; i++) {
+ // std::cout << "row #" << i << ": ";
+
+ // for (size_t j = 0; j < 1280; ++j) {
+ // const auto& p = ::calibrationTableX->at(j).at(i);
+ // std::cout << p << ' ';
+ // }
+
+ // std::cout << std::endl;
+ // }
+
+ // exit(EXIT_SUCCESS);
+ interpolate(::calibrationTableX);
+
+ // calibrationTableToImage(::calibrationTableX)
+ // .save("/home/user/dumps/imageX_interpolated.png");
+ }
+
+ // load binary calibration dumps and calibrate
+ if (false)
+ {
+ if (true)
+ {
+ auto rawProfiles = openDump("/home/user/dumps/binz");
+ // auto rawProfiles = openDump("/home/user/dumps/z");
+ qDebug() << "raw z-profiles count is" << rawProfiles.size();
+ // qDebug() << "height" << calibrationColumnHeight;
+
+ auto filteredRawProfiles = filter(std::move(rawProfiles));
+ qDebug() << "filtered z-profiles count is"
+ << filteredRawProfiles.count();
+
+ ::calibrationTableZ = calibrateZ(std::move(filteredRawProfiles),
+ requested_params.stepsPerMm);
+
+ // bool ok = calibrationTableToImage(::calibrationTableZ)
+ // .save("/home/user/dumps/z/imageZ.png");
+
+ // if (!ok)
+ // {
+ // qDebug() << "cannot save imageZ.png";
+ // exit(EXIT_FAILURE);
+ // }
+
+ interpolate(::calibrationTableZ);
+
+ if (!dump(::calibrationTableZ,
+ "/home/user/dumps/binz.calibration_table"))
+ {
+ qApp->exit(EXIT_FAILURE);
+ }
+ // calibrationTableToImage(::calibrationTableZ)
+ // .save("/home/user/dumps/z/imageZ_interpolated.png");
+ // exit(EXIT_SUCCESS);
+ }
+
+ qDebug()
+ << "--------------------------------------------------------";
+
+ if (true)
+ {
+ auto rawProfiles = openDump("/home/user/dumps/binx");
+ qDebug() << "raw x-profiles count is" << rawProfiles.size();
+ // qDebug() << "height" << calibrationColumnHeight;
+
+ auto filteredRawProfiles = filter(std::move(rawProfiles));
+ qDebug() << "filtered x-profiles count is"
+ << filteredRawProfiles.count();
+
+ ::calibrationTableX = calibrateX(std::move(filteredRawProfiles));
+
+ // bool ok = calibrationTableToImage(::calibrationTableX)
+ // .save("/home/user/dumps/z/imageX.png");
+
+ // if (!ok)
+ // {
+ // qDebug() << "cannot save imageX.png";
+ // exit(EXIT_FAILURE);
+ // }
+
+ interpolate(::calibrationTableX);
+
+ if (!dump(::calibrationTableX,
+ "/home/user/dumps/binx.calibration_table"))
+ {
+ qApp->exit(EXIT_FAILURE);
+ }
+
+ // calibrationTableToImage(::calibrationTableX)
+ // .save("/home/user/dumps/z/imageX_interpolated.png");
+ }
+ }
+ }
+
+ // exit(EXIT_SUCCESS);
+
+ // if (!initLaser()) {
+ // return EXIT_FAILURE;
+ // }
+
+ // PrinterClient printerClient;
+
+ QElapsedTimer t;
+ t.start();
+
+ qDebug() << "msecs before encoder:" << t.elapsed();
+
+ // RotaryEncoder encoder;
+
+ qDebug() << "msecs before camera:" << t.elapsed();
+ // FIXME: don't use one var for everything
+ int ret;
+#ifndef INNO_MAKER
+ std::unique_ptr<libcamera::CameraManager> cm =
+ std::make_unique<libcamera::CameraManager>();
+ cm->start();
+#endif
+ // const auto cameras = cm->cameras();
+ // const auto cameras = OV9281::search(cm);
+ const auto cameras = InnoMakerOV9281::search();
+ // const auto cameras =
+
+ if (cameras.empty())
+ {
+ std::cerr << "No cameras were identified on the system." << std::endl;
+#ifndef INNO_MAKER
+ cm->stop();
+#endif
+
+ return EXIT_FAILURE;
+ }
+
+ auto camera = cameras.at(0);
+
+#ifndef INNO_MAKER
+ camera->printControls();
+#endif
+
+ std::cout << "connect everything" << std::endl;
+ // camera->newPixels.connect(&onNewPixels);
+ // camera->newImage.connect(&onNewImage);
+ camera->newImageCallback = &onNewImage;
+ camera->newPixelsCallback = &onNewPixels;
+
+ for (auto& i : initializers)
+ i.waitForFinished();
+ std::cout << "loaded calibration tables" << std::endl;
+
+ if (!camera->startStream())
+ {
+#ifndef INNO_MAKER
+ cm->stop();
+#endif
+
+ return EXIT_FAILURE;
+ }
+
+ QHttpServer qHttpServer;
+ qHttpServer.route("/v1/sensor/image", [&]() {
+ // std::cout << "http: image" << std::endl << std::flush;
+ // FILE *f = fopen("/tmp/img.pgm", "w");
+ // static bool save = false;
+ pgm_save(::img);
+ // save = false;
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+ // qDebug() << "mutex locked";
+ // qDebug() << "image saved to array";
+ return QByteArray((const char*) pgm_image, pgm_image_size);
+ });
+ qHttpServer.route("/v1/sensor/image2", [&]() {
+ // std::cout << "http: image2" << std::endl;
+ pgm_save(::img);
+
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+ // qDebug() << "image2";
+ return QByteArray((const char*) pgm_image, pgm_image_size);
+ });
+ // qHttpServer.route("/v1/sensor/exposureTimeUs", [&]() {
+ // // std::lock_guard<std::mutex> lg(pgm_image_mtx);
+ // return "123";
+ // });
+ qHttpServer.route("/v1/pixels", [&]() {
+ // std::cout << "http: pixels" << std::endl;
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+
+ QJsonArray pixels;
+
+ for (size_t i = 0; i < img_width; ++i)
+ {
+ // pixels << img_height - img.pixels[i];
+ pixels << ::pixels.pixels[i];
+ }
+
+ QJsonObject json;
+ json["pixels"] = pixels;
+ // json["encoderPosition"] = qint64{encoder.position()};
+ // FIXME: get prom pixels struct
+ json["measurementCounter"] = qint64{img->counters.measurementCounter};
+ json["timestampUs"] = qint64(img->counters.timestampUs);
+
+ const auto lines = pixelsToLines(::pixels);
+
+ // qDebug() << "lines count is " << lines.count();
+
+ QJsonArray jsonLines;
+
+ for (const auto& l : lines)
+ {
+ jsonLines << QJsonArray{QJsonArray{l.p1().x(), l.p1().y()},
+ QJsonArray{l.p2().x(), l.p2().y()}};
+ }
+
+ json["lines"] = jsonLines;
+
+ return QHttpServerResponse(QJsonDocument(json).toJson());
+ });
+
+ qHttpServer.route("/v1/profile", [&]() -> QHttpServerResponse {
+ // std::cout << "http: profile" << std::endl;
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+
+ if (!::calibrationTableZ || !::calibrationTableX)
+ return QHttpServerResponse::StatusCode::ServiceUnavailable;
+
+ const Profile profile(::pixels,
+ ::calibrationTableZ,
+ ::calibrationTableX);
+
+ const QJsonObject json{{"profile", QJsonObject(profile)}};
+
+ return QHttpServerResponse(QJsonDocument(json).toJson());
+ });
+
+ qHttpServer
+ .route("/v1/commands/resetEncoder",
+ [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ std::cout << "http: resetEncoder" << std::endl;
+ if (request.method() != QHttpServerRequest::Method::Post)
+ {
+ return QHttpServerResponse::StatusCode::NotFound;
+ }
+
+ qDebug() << "reset encoder";
+
+ positionSteps = 0;
+
+ return QHttpServerResponse::StatusCode::Ok;
+ });
+
+ qHttpServer
+ .route("/v1/commands/startCalibration",
+ [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ std::cout << "http: startCalibration" << std::endl;
+ if (request.method() != QHttpServerRequest::Method::Post)
+ {
+ return QHttpServerResponse::StatusCode::NotFound;
+ }
+
+ qDebug() << "start calibration";
+
+ // TODO: use flags
+ scanningModeFlags = ScanningModeFlags::Calibration;
+ calibrationTimer.start();
+
+ return QHttpServerResponse::StatusCode::Ok;
+ });
+
+ qHttpServer
+ .route("/v1/commands/gCode",
+ [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ std::cout << "http: gCode" << std::endl;
+ if (request.method() != QHttpServerRequest::Method::Post)
+ {
+ return QHttpServerResponse::StatusCode::NotFound;
+ }
+
+ const auto command = request.body();
+
+ qDebug() << "send gCode:" << command;
+
+ // printerClient.sendCommand(command);
+
+ return QHttpServerResponse::StatusCode::Ok;
+ });
+
+ // qHttpServer
+ // .route("/v1/commands/startCalibration",
+ // [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ // std::cout << "http: startCalibration" << std::endl;
+ // if (request.method() != QHttpServerRequest::Method::Post)
+ // {
+ // return QHttpServerResponse::StatusCode::NotFound;
+ // }
+
+ // const auto command = request.body();
+
+ // qDebug() << "send gCode:" << command;
+
+ // // printerClient.sendCommand(command);
+
+ // return QHttpServerResponse::StatusCode::Ok;
+ // });
+
+ qHttpServer.route(
+ "/v1/sensor/params",
+ [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ // std::cout << "http: params" << std::endl;
+ switch (request.method())
+ {
+ case QHttpServerRequest::Method::Get: {
+ std::lock_guard<std::mutex> lg(pgm_image_mtx);
+ QJsonObject json;
+
+ // const libcamera::ControlIdMap& ctrlIdMap =
+ // camera->controls().idmap();
+
+ // qDebug() << "readParams:" << lastControls.size();
+ // qDebug() << request.method();
+
+ // for (const auto& [id, value] : lastControls)
+ // {
+ // const libcamera::ControlId* controlId = ctrlIdMap.at(id);
+ // auto name = QString::fromStdString(controlId->name());
+ // const auto valueStr =
+ // QString::fromStdString(value.toString());
+ // qDebug()
+ // << "\t param:" << controlId->id() << name << valueStr;
+
+ // name[0] = name[0].toLower();
+ // json[name] = valueStr;
+ // }
+
+ // json[laserLevelKey] = requested_params.laserLevel;
+
+ // qDebug() << "response body:" << json;
+
+ // QHttpServerResponse
+ return QHttpServerResponse(QJsonDocument(json).toJson());
+ }
+
+ case QHttpServerRequest::Method::Post: {
+ // qDebug() << "request body:" << request.body();
+
+ auto json = QJsonDocument::fromJson(request.body()).object();
+
+ if (json.contains(exposureTimeKey))
+ {
+ const int32_t value{json[exposureTimeKey].toInt()};
+
+ if (value == 0)
+ return QHttpServerResponse::StatusCode::
+ RequestRangeNotSatisfiable;
+
+ // qDebug() << "set new exposure time:" << value;
+
+ // requested_params.exposureTime = value;
+ if (!camera->setExposureTimeUs(value))
+ return QHttpServerResponse::StatusCode::
+ RequestRangeNotSatisfiable;
+ }
+
+ if (json.contains(gainKey))
+ {
+ const int32_t value{json[gainKey].toInt()};
+
+ if (value == 0)
+ return QHttpServerResponse::StatusCode::
+ RequestRangeNotSatisfiable;
+
+ // qDebug() << "set gain:" << value;
+
+ // requested_params.exposureTime = value;
+ if (!camera->setGain(value))
+ return QHttpServerResponse::StatusCode::
+ RequestRangeNotSatisfiable;
+ }
+
+ if (json.contains(laserLevelKey))
+ {
+ const int32_t value{json[laserLevelKey].toInt()};
+
+ // if (value == 0)
+ // {
+ // return QHttpServerResponse::StatusCode::NotFound;
+ // }
+
+ // qDebug() << "set new laserLevel:" << value;
+ if (!camera->setLaserLevel(value))
+ return QHttpServerResponse::StatusCode::
+ RequestRangeNotSatisfiable;
+
+ requested_params.laserLevel = value;
+
+ // const QString laserLevelFile{
+ // "/sys/class/pwm/pwmchip2/pwm1/duty_cycle"};
+ // QFile f{laserLevelFile};
+
+ // if (!f.open(QFile::ReadWrite))
+ // {
+ // qDebug() << "cannot open laser level file:"
+ // << f.errorString();
+ // qDebug() << "file path is" << f.fileName();
+ // return QHttpServerResponse::StatusCode::InternalServerError;
+ // }
+
+ // QTextStream s{&f};
+
+ // s << value;
+
+ // s >> requested_params.laserLevel;
+
+ // qDebug() << "done with laser level";
+ }
+
+ return QHttpServerResponse(request.body());
+ }
+ default: {
+ return QHttpServerResponse(
+ QByteArray("unsupported http method"));
+ }
+ }
+ });
+
+ qDebug() << "listen: " << qHttpServer.listen(QHostAddress::Any, 8081);
+
+ QFuture<void> future = QtConcurrent::run([]() {
+ Port port(8080);
+ Address addr(Ipv4::any(), port);
+
+ HttpService httpService(addr);
+
+ size_t threads_count = 1;
+ httpService.init(threads_count);
+ httpService.start();
+ });
+
+ ////////////////////////////////////////////////////////////////////////////
+ std::clog << std::flush;
+ std::cerr << std::flush;
+ std::cout << "ok for now" << std::endl << std::flush;
+
+ // camera->stop();
+ // camera->release();
+ // cm->stop();
+
+ auto result = app.exec();
+
+ future.cancel();
+ future.waitForFinished();
+
+ // for (auto& [fd, mem] : mappedBuffers_)
+ // {
+ // munmap(mem.first, mem.second);
+ // }
+
+ // FIXME: crash somewhere here. proper libcamera finishing needed
+ // requests.clear();
+ // mappedBuffers_.clear();
+
+ // camera->stop();
+ // config.reset();
+ // allocator->free(stream);
+ // allocator.reset();
+ // camera->release();
+ // camera.reset();
+#ifndef INNO_MAKER
+ cm->stop();
+#endif
+
+ return result;
+}
+
+bool initLaser()
+{
+ const QLatin1String pwmChip{"pwmchip2"};
+ const uint16_t pwmChannel{1};
+ const QLatin1String pwmSystemRoot{"/sys/class/pwm"};
+ const QString pwmChipRoot{pwmSystemRoot + "/" + pwmChip};
+
+ const QString pwmExportFile{pwmChipRoot + "/export"};
+
+ QFile f{pwmExportFile};
+
+ if (!f.open(QFile::WriteOnly))
+ {
+ qWarning() << "cannot open" << f.fileName() << "for writing";
+ qWarning() << "error:" << f.errorString();
+
+ return false;
+ }
+
+ QTextStream s{&f};
+ s << pwmChannel;
+
+ const QString pwm{QLatin1String("pwm%1").arg(QString::number(pwmChannel))};
+ const QString pwmRoot{pwmChipRoot + "/" + pwm};
+
+ const QString periodFilename{pwmRoot + "/period"};
+ f.close();
+ f.setFileName(periodFilename);
+
+ if (!f.open(QFile::WriteOnly))
+ {
+ qWarning() << "cannot open" << f.fileName() << "for writing";
+ qWarning() << "error:" << f.errorString();
+
+ return false;
+ }
+
+ const unsigned periodHz{50'000};
+
+ s << periodHz;
+
+ const QString dutyCycleFilename{pwmRoot + "/duty_cycle"};
+ f.close();
+ f.setFileName(dutyCycleFilename);
+
+ if (!f.open(QFile::WriteOnly))
+ {
+ qWarning() << "cannot open" << f.fileName() << "for writing";
+ qWarning() << "error:" << f.errorString();
+
+ return false;
+ }
+
+ const unsigned dutyCycle{3'000};
+
+ s << dutyCycle;
+
+ const QString enableFilename{pwmRoot + "/enable"};
+ f.close();
+ f.setFileName(enableFilename);
+
+ if (!f.open(QFile::WriteOnly))
+ {
+ qWarning() << "cannot open" << f.fileName() << "for writing";
+ qWarning() << "error:" << f.errorString();
+
+ return false;
+ }
+
+ const int enable{1};
+
+ s << enable;
+
+ return true;
+}