Vincent Molenaar 2 years ago
commit
7004332004
10 changed files with 1045 additions and 0 deletions
  1. 73 0
      data/Notes.txt
  2. 53 0
      data/index.html
  3. 366 0
      data/reset.css
  4. 168 0
      data/script.js
  5. 27 0
      data/style.css
  6. 39 0
      include/README
  7. 46 0
      lib/README
  8. 21 0
      platformio.ini
  9. 241 0
      src/main.cpp
  10. 11 0
      test/README

+ 73 - 0
data/Notes.txt

@@ -0,0 +1,73 @@
+Formbuilder 
+
+https://beautifytools.com/html-form-builder.php
+
+[
+    {
+        "type": "number",
+        "required": false,
+        "label": "Module Offset",
+        "placeholder": "0",
+        "className": "form-control",
+        "name": "modOffset",
+        "access": false,
+        "value": "0",
+        "min": 0,
+        "max": 1024
+    },
+    {
+        "type": "number",
+        "required": false,
+        "label": "Input Offset",
+        "placeholder": "0",
+        "className": "form-control",
+        "name": "inputOffset",
+        "access": false,
+        "value": "0",
+        "min": 0,
+        "max": 1024
+    },
+    {
+        "type": "number",
+        "required": false,
+        "label": "Auto update interval (seconds)",
+        "placeholder": "2",
+        "className": "form-control",
+        "name": "updateInterval",
+        "access": false,
+        "value": "2",
+        "min": 0,
+        "max": 60
+    },
+    {
+        "type": "checkbox-group",
+        "required": false,
+        "label": "checkoptions",
+        "toggle": false,
+        "inline": false,
+        "name": "checkoptions",
+        "access": false,
+        "other": false,
+        "values": [
+            {
+                "label": "AutoUpdate",
+                "value": "autoupdate",
+                "selected": false
+            },
+            {
+                "label": "Emulate S88 Master",
+                "value": "emulateMaster",
+                "selected": false
+            }
+        ]
+    },
+    {
+        "type": "button",
+        "label": "Save",
+        "subtype": "button",
+        "className": "btn-success btn",
+        "name": "button-save",
+        "access": false,
+        "style": "success"
+    }
+]

+ 53 - 0
data/index.html

@@ -0,0 +1,53 @@
+<html>
+    <head>
+        <link rel="stylesheet" href="reset.css">
+        <link rel="stylesheet" href="style.css">
+        
+    </head>
+    <body>
+        <details>
+            <summary>Settings</summary>
+            <div id="settings">
+                <div class="rendered-form">
+                    <div class="formbuilder-number form-group field-modOffset">
+                        <label for="modOffset" class="formbuilder-number-label">Module Offset</label>
+                        <input type="number" placeholder="0" class="form-control" name="modOffset" access="false" value="0" min="0" max="1024" id="modOffset">
+                    </div>
+                    <div class="formbuilder-number form-group field-inputOffset">
+                        <label for="inputOffset" class="formbuilder-number-label">Input Offset</label>
+                        <input type="number" placeholder="0" class="form-control" name="inputOffset" access="false" value="0" min="0" max="1024" id="inputOffset">
+                    </div>
+                    <div class="formbuilder-number form-group field-updateInterval">
+                        <label for="updateInterval" class="formbuilder-number-label">Auto update interval (seconds)</label>
+                        <input type="number" placeholder="2" class="form-control" name="updateInterval" access="false" value="2" min="0" max="60" id="updateInterval">
+                    </div>
+                    <div class="formbuilder-checkbox-group form-group field-checkoptions">
+                        <label for="checkoptions" class="formbuilder-checkbox-group-label">checkoptions</label>
+                        <div class="checkbox-group">
+                            <div class="formbuilder-checkbox">
+                                <input name="checkoptions[]" access="false" id="checkoptions-0" value="autoupdate" type="checkbox">
+                                <label for="checkoptions-0">AutoUpdate</label>
+                            </div>
+                            <div class="formbuilder-checkbox">
+                                <input name="checkoptions[]" access="false" id="checkoptions-1" value="emulateMaster" type="checkbox">
+                                <label for="checkoptions-1">Emulate S88 Master</label>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="formbuilder-button form-group field-button-save">
+                        <button type="button" class="btn-success btn" name="button-save" access="false" style="success" id="button-save">Save local</button>
+                        <button type="button" class="btn-success btn" name="button-save-device" access="false" style="success" id="button-save-device">Save on device</button>
+                        <button type="button" class="btn-success btn" name="button-load-device" access="false" style="success" id="button-load-device">Load from device</button>
+                    </div>
+                </div>
+            </div>
+          </details>
+          <button type="button" class="btn-success btn" name="button-update" access="false" style="success" id="button-update">Manual update</button>
+        <div id="lastUpdate"></div>
+        <table id="overview">
+
+        </table>
+        
+    </body>
+    <script src="script.js"></script>
+</html> 

+ 366 - 0
data/reset.css

@@ -0,0 +1,366 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+   v2.0-modified | 20110126
+   License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+  margin: 0;
+	padding: 0;
+	border: 0;
+	font-size: 100%;
+	font: inherit;
+	vertical-align: baseline;
+}
+
+/* make sure to set some focus styles for accessibility */
+:focus {
+    outline: 0;
+}
+
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+	display: block;
+}
+
+body {
+	line-height: 1;
+}
+
+ol, ul {
+	list-style: none;
+}
+
+blockquote, q {
+	quotes: none;
+}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+	content: '';
+	content: none;
+}
+
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}
+
+input[type=search]::-webkit-search-cancel-button,
+input[type=search]::-webkit-search-decoration,
+input[type=search]::-webkit-search-results-button,
+input[type=search]::-webkit-search-results-decoration {
+    -webkit-appearance: none;
+    -moz-appearance: none;
+}
+
+input[type=search] {
+    -webkit-appearance: none;
+    -moz-appearance: none;
+    -webkit-box-sizing: content-box;
+    -moz-box-sizing: content-box;
+    box-sizing: content-box;
+}
+
+textarea {
+    overflow: auto;
+    vertical-align: top;
+    resize: vertical;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
+ */
+
+audio,
+canvas,
+video {
+    display: inline-block;
+    *display: inline;
+    *zoom: 1;
+    max-width: 100%;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+    display: none;
+    height: 0;
+}
+
+/**
+ * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
+ * Known issue: no IE 6 support.
+ */
+
+[hidden] {
+    display: none;
+}
+
+/**
+ * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
+ *    `em` units.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ *    user zoom.
+ */
+
+html {
+    font-size: 100%; /* 1 */
+    -webkit-text-size-adjust: 100%; /* 2 */
+    -ms-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+    outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+    outline: 0;
+}
+
+/**
+ * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
+ * 2. Improve image quality when scaled in IE 7.
+ */
+
+img {
+    border: 0; /* 1 */
+    -ms-interpolation-mode: bicubic; /* 2 */
+}
+
+/**
+ * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
+ */
+
+figure {
+    margin: 0;
+}
+
+/**
+ * Correct margin displayed oddly in IE 6/7.
+ */
+
+form {
+    margin: 0;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+    border: 1px solid #c0c0c0;
+    margin: 0 2px;
+    padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct color not being inherited in IE 6/7/8/9.
+ * 2. Correct text not wrapping in Firefox 3.
+ * 3. Correct alignment displayed oddly in IE 6/7.
+ */
+
+legend {
+    border: 0; /* 1 */
+    padding: 0;
+    white-space: normal; /* 2 */
+    *margin-left: -7px; /* 3 */
+}
+
+/**
+ * 1. Correct font size not being inherited in all browsers.
+ * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
+ *    and Chrome.
+ * 3. Improve appearance and consistency in all browsers.
+ */
+
+button,
+input,
+select,
+textarea {
+    font-size: 100%; /* 1 */
+    margin: 0; /* 2 */
+    vertical-align: baseline; /* 3 */
+    *vertical-align: middle; /* 3 */
+}
+
+/**
+ * Address Firefox 3+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+    line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+    text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ *    and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ *    `input` and others.
+ * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
+ *    Known issue: inner spacing remains in IE 6.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+    -webkit-appearance: button; /* 2 */
+    cursor: pointer; /* 3 */
+    *overflow: visible;  /* 4 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+    cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to content-box in IE 8/9.
+ * 2. Remove excess padding in IE 8/9.
+ * 3. Remove excess padding in IE 7.
+ *    Known issue: excess padding remains in IE 6.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+    box-sizing: border-box; /* 1 */
+    padding: 0; /* 2 */
+    *height: 13px; /* 3 */
+    *width: 13px; /* 3 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ *    (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+    -webkit-appearance: textfield; /* 1 */
+    -moz-box-sizing: content-box;
+    -webkit-box-sizing: content-box; /* 2 */
+    box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+    -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 3+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+    border: 0;
+    padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 6/7/8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+    overflow: auto; /* 1 */
+    vertical-align: top; /* 2 */
+}
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+    border-collapse: collapse;
+    border-spacing: 0;
+}
+
+html,
+button,
+input,
+select,
+textarea {
+    color: #222;
+}
+
+
+::-moz-selection {
+    background: #b3d4fc;
+    text-shadow: none;
+}
+
+::selection {
+    background: #b3d4fc;
+    text-shadow: none;
+}
+
+img {
+    vertical-align: middle;
+}
+
+fieldset {
+    border: 0;
+    margin: 0;
+    padding: 0;
+}
+
+textarea {
+    resize: vertical;
+}
+
+.chromeframe {
+    margin: 0.2em 0;
+    background: #ccc;
+    color: #000;
+    padding: 0.2em 0;
+}

+ 168 - 0
data/script.js

@@ -0,0 +1,168 @@
+var rawContent;
+var data = [];
+var loading = false;
+var websock;
+
+var settings = {
+  modOffset: 0,
+  inputOffset: 0,
+  updateInterval: 2,
+  autoupdate: false,
+  emulateMaster: false
+}
+
+var autoRefreshInterval;
+
+function GetData() {
+  if (loading)
+    return;
+  loading = true;
+  return fetch("/data")
+    .then(function(result) {
+      return result.text().then(function(text) {
+        rawContent = text;
+        data = ConvertData(rawContent);
+        loading = false;
+      });
+    })
+    .catch(function() {
+        loading = false;
+    });
+}
+
+function ConvertData(rawContent) {
+  let dataResult = [];
+
+  var tmpList = rawContent.split('~');
+  tmpList.forEach(resultLine => {
+    var tmpResultLine = resultLine.split(":");
+    var tmpModule = tmpResultLine[0].split(".");
+    var dataObj = {
+      module: parseInt(tmpModule[0]),
+      input: parseInt(tmpModule[1]),
+      data: (tmpResultLine[1] == 1)
+    }
+    if (!isNaN(dataObj.input) && !isNaN(dataObj.module))
+      dataResult.push(dataObj);
+  });
+  return dataResult;
+}
+
+function RenderData(data) {
+  var table = document.getElementById("overview");
+  table.innerHTML = "";
+  var lastModule = -1;
+  var row;
+  data.forEach(element => {
+    if (lastModule != element.module) {
+      lastModule = element.module
+      row = table.insertRow(element.module);
+      var title = row.insertCell(0);
+      title.innerHTML = (element.module + settings.modOffset);
+    }
+    var cell = row.insertCell(element.input + 1);
+    cell.innerHTML = (element.input + settings.inputOffset);
+    cell.classList.add('input-value')
+    cell.classList.add('input-value-' + ((element.data) ? "ON" : "OFF"))
+  });
+  document.getElementById("lastUpdate").innerHTML = new Date().toLocaleString();
+}
+
+function SaveSettings() {
+  settings.modOffset = parseInt(document.getElementById("modOffset").value);
+  settings.inputOffset = parseInt(document.getElementById("inputOffset").value);
+  settings.updateInterval = parseInt(document.getElementById("updateInterval").value);
+  settings.autoupdate = document.getElementById("checkoptions-0").checked
+  settings.emulateMaster = document.getElementById("checkoptions-1").checked
+  window.localStorage.setItem('settings', JSON.stringify(settings));
+  SetAutoUpdate();
+}
+
+function LoadSettings(rawSettings) {
+    if(rawSettings == null){
+        var tmpsettings = JSON.parse(window.localStorage.getItem('settings'));
+        if (tmpsettings == null)
+            return;
+        settings = tmpsettings;
+    }else{
+        settings = rawSettings;
+    }
+
+    document.getElementById("modOffset").value = settings.modOffset;
+    document.getElementById("inputOffset").value = settings.inputOffset;
+    document.getElementById("updateInterval").value = settings.updateInterval;
+    document.getElementById("checkoptions-0").checked = settings.autoupdate;
+    document.getElementById("checkoptions-1").checked = settings.emulateMaster;
+    SetAutoUpdate();
+}
+
+function SaveSettingsOnDevice(){
+    SaveSettings();
+    var urlEncodedSettings = encodeURI(JSON.stringify(settings));
+    var url = "/config?key=settings&value="+urlEncodedSettings;
+
+    fetch(url)
+    .then(function(result) {
+      result.text().then(function(text) {
+        alert("Saved")
+      });
+    })
+    .catch(function() {
+    });
+}
+
+function LoadSettingsFromDevice(){
+    fetch("/config?key=settings")
+    .then(function(result) {
+      return result.text().then(function(text) {
+        var settingsraw = JSON.parse(decodeURI(text));
+        LoadSettings(settingsraw);
+      });
+    })
+    .catch(function() {
+        loading = false;
+    });
+}
+
+function SetAutoUpdate() {
+  if (autoRefreshInterval != null)
+    clearInterval(autoRefreshInterval)
+  autoRefreshInterval = setInterval(() => {
+    if (settings.autoupdate) {
+      if (loading)
+        return;
+      DoUpdate();
+    }
+  }, settings.updateInterval*1000);
+}
+
+function DoUpdate() {
+  if (loading)
+    return;
+  GetData().then(() => {
+    RenderData(data)
+  });
+}
+
+function StartSocket() {
+    websock = new WebSocket('ws://' + window.location.hostname + ':81/');
+    websock.onopen = function(evt) { console.log('websock open'); };
+    websock.onclose = function(evt) { console.log('websock close'); };
+    websock.onerror = function(evt) { console.log(evt); };
+    websock.onmessage = function(evt) {
+      console.log(evt);
+        switch(evt.data){
+            case "update":
+                DoUpdate();
+            break;
+        }
+    };
+}
+
+LoadSettings();
+DoUpdate();
+StartSocket();
+document.getElementById("button-save").addEventListener("click", SaveSettings);
+document.getElementById("button-save-device").addEventListener("click", SaveSettingsOnDevice);
+document.getElementById("button-load-device").addEventListener("click", LoadSettingsFromDevice);
+document.getElementById("button-update").addEventListener("click", DoUpdate);

+ 27 - 0
data/style.css

@@ -0,0 +1,27 @@
+.input-value {
+    min-width: 1em;
+    text-align: center;
+}
+
+.input-value-ON {
+    background-color: green;
+    color: white;
+}
+
+.input-value-OFF {
+    background-color: red;
+    color: white;
+}
+
+summary {
+    padding: 1em;
+}
+
+#settings {
+    padding: 0.5em;
+}
+
+#settings label {
+    min-width: 8em;
+    display: inline-block;
+}

+ 39 - 0
include/README

@@ -0,0 +1,39 @@
+
+This directory is intended for project header files.
+
+A header file is a file containing C declarations and macro definitions
+to be shared between several project source files. You request the use of a
+header file in your project source file (C, C++, etc) located in `src` folder
+by including it, with the C preprocessing directive `#include'.
+
+```src/main.c
+
+#include "header.h"
+
+int main (void)
+{
+ ...
+}
+```
+
+Including a header file produces the same results as copying the header file
+into each source file that needs it. Such copying would be time-consuming
+and error-prone. With a header file, the related declarations appear
+in only one place. If they need to be changed, they can be changed in one
+place, and programs that include the header file will automatically use the
+new version when next recompiled. The header file eliminates the labor of
+finding and changing all the copies as well as the risk that a failure to
+find one copy will result in inconsistencies within a program.
+
+In C, the usual convention is to give header files names that end with `.h'.
+It is most portable to use only letters, digits, dashes, and underscores in
+header file names, and at most one dot.
+
+Read more about using header files in official GCC documentation:
+
+* Include Syntax
+* Include Operation
+* Once-Only Headers
+* Computed Includes
+
+https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

+ 46 - 0
lib/README

@@ -0,0 +1,46 @@
+
+This directory is intended for project specific (private) libraries.
+PlatformIO will compile them to static libraries and link into executable file.
+
+The source code of each library should be placed in a an own separate directory
+("lib/your_library_name/[here are source files]").
+
+For example, see a structure of the following two libraries `Foo` and `Bar`:
+
+|--lib
+|  |
+|  |--Bar
+|  |  |--docs
+|  |  |--examples
+|  |  |--src
+|  |     |- Bar.c
+|  |     |- Bar.h
+|  |  |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
+|  |
+|  |--Foo
+|  |  |- Foo.c
+|  |  |- Foo.h
+|  |
+|  |- README --> THIS FILE
+|
+|- platformio.ini
+|--src
+   |- main.c
+
+and a contents of `src/main.c`:
+```
+#include <Foo.h>
+#include <Bar.h>
+
+int main (void)
+{
+  ...
+}
+
+```
+
+PlatformIO Library Dependency Finder will find automatically dependent
+libraries scanning project source files.
+
+More information about PlatformIO Library Dependency Finder
+- https://docs.platformio.org/page/librarymanager/ldf.html

+ 21 - 0
platformio.ini

@@ -0,0 +1,21 @@
+; PlatformIO Project Configuration File
+;
+;   Build options: build flags, source filter
+;   Upload options: custom upload port, speed and extra flags
+;   Library options: dependencies, extra library storages
+;   Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+[env:d1_mini]
+platform = espressif8266
+board = d1_mini
+framework = arduino
+monitor_speed = 115200
+; change MCU frequency
+board_build.f_cpu = 160000000L
+board_build.filesystem = littlefs
+
+lib_deps =
+    links2004/WebSockets @ 2.3.6

+ 241 - 0
src/main.cpp

@@ -0,0 +1,241 @@
+#include <Arduino.h>
+#include "LittleFS.h"
+#include <ESP8266WiFi.h>
+#include <ESP8266WebServer.h>
+#include <WebSocketsServer.h>
+
+const char *SSID = "S88Sniffer";
+const char *PASSWORD = "12345678";
+const int MAXMODULECOUNT = 16; // 16 * 8 inputs
+const bool DEBUGOUTPUT = false;
+
+/*
+PINOUT PCB
+D7 = 7 - Reset
+D6 = 6 - Load
+D2 = 2 - Data
+D4 = 4 - Clock
+
+*/
+
+ESP8266WebServer httpServer(80);
+WebSocketsServer webSocket = WebSocketsServer(81);
+
+int clockPin = D4;
+int dataPin = D2;
+int loadPin = D6;
+int resetPin = D7;
+
+int clockCounter = 0;
+long loopCounter = 0;
+long moduleCounter = 0;
+
+unsigned int data[MAXMODULECOUNT];
+
+bool loadSensors = false;
+bool resetTriggered = false;
+bool loadTriggered = false;
+bool somthingChanged = false;
+bool interuptsSet = false;
+
+unsigned long lastClockChange;
+unsigned long loadStartedAt;
+
+void ICACHE_RAM_ATTR s88Clock()
+{
+  delayMicroseconds(16); //Delay makes reading output signal more reliable.
+  int val = digitalRead(dataPin);
+  int oldVal = bitRead(data[moduleCounter], clockCounter);
+
+  if (val != oldVal)
+  {
+    somthingChanged = true;
+  }
+
+  bitWrite(data[moduleCounter], clockCounter, val);
+
+  if ((clockCounter + 1) == 8)
+  {
+    moduleCounter++;
+  }
+
+  clockCounter = (clockCounter + 1) % 8;
+}
+
+void ICACHE_RAM_ATTR s88Load()
+{
+  clockCounter = 0;
+  moduleCounter = 0;
+  loopCounter++;
+  loadTriggered = true;
+}
+
+void ICACHE_RAM_ATTR s88Reset()
+{
+  resetTriggered = true;
+}
+
+void handleData()
+{
+  String resp = "";
+  for (int i = 0; i < MAXMODULECOUNT; i++)
+  {
+    //resp += (data[i]);
+    for (int j = 0; j < 8; j++)
+    {
+      resp += ((String(i) + "." + String(j) + ":" + String(bitRead(data[i], j))) + "~");
+    }
+  }
+  httpServer.send(200, "text/html", resp);
+}
+
+void handleSocketTest()
+{
+  webSocket.broadcastTXT("update");
+  httpServer.send(200, "text/html", "OK");
+}
+
+void handleConfigs()
+{
+  if (httpServer.arg("key") != "")
+  {
+    String key = httpServer.arg("key");
+    if (httpServer.arg("value") != "")
+    {
+      File f = LittleFS.open(String("/config-" + key), "w");
+      String value = httpServer.arg("value");
+      f.write(value.c_str());
+      httpServer.send(200, "text/html", "OK");
+      f.close();
+    }
+    else
+    {
+      File f = LittleFS.open(String("/config-" + key), "r");
+      httpServer.send(200, "text/html", f.readString());
+      f.close();
+    }
+  }
+  else
+  {
+    httpServer.send(400, "text/html", "must give a key");
+  }
+}
+
+void setEmulation(bool emulationActive)
+{
+  if (emulationActive)
+  {
+    pinMode(clockPin, OUTPUT);
+    pinMode(loadPin, OUTPUT);
+    pinMode(resetPin, OUTPUT);
+  }
+  else
+  {
+    pinMode(clockPin, INPUT);
+    pinMode(loadPin, INPUT);
+    pinMode(resetPin, INPUT);
+  }
+}
+
+void runEmulation()
+{
+  if(loadStartedAt+500 > micros()){
+    digitalWrite(loadPin,HIGH);
+    return;
+  }
+
+  if(loadStartedAt+1000 > micros()){
+    digitalWrite(loadPin,LOW);
+    return;
+  }
+
+  if (lastClockChange + 300 < micros())
+  {
+    lastClockChange = micros();
+    digitalWrite(clockPin, !digitalRead(clockPin));
+  }
+
+  if(moduleCounter == MAXMODULECOUNT){
+    loadStartedAt = micros();
+    s88Load();
+  }
+}
+
+void setupInterupts(){
+  interuptsSet = true;
+  attachInterrupt(digitalPinToInterrupt(clockPin), s88Clock, RISING);
+  attachInterrupt(digitalPinToInterrupt(loadPin), s88Load, RISING);
+  attachInterrupt(digitalPinToInterrupt(resetPin), s88Reset, RISING);
+}
+
+void setup()
+{
+  pinMode(dataPin, INPUT);
+  setEmulation(false);
+  Serial.begin(115200);
+  WiFi.softAP(SSID, PASSWORD);
+  // put your setup code here, to run once:
+  if (!LittleFS.begin())
+  {
+    Serial.println("An Error has occurred while mounting LittleFS");
+    return;
+  }
+
+  httpServer.on("/data", handleData);
+  httpServer.on("/test", handleSocketTest);
+  httpServer.on("/config", handleConfigs);
+  httpServer.serveStatic("/index.html", LittleFS, "/index.html", "max-age=43200");
+  httpServer.serveStatic("/", LittleFS, "/index.html", "max-age=43200");
+  httpServer.serveStatic("/script.js", LittleFS, "/script.js", "max-age=43200");
+  httpServer.serveStatic("/reset.css", LittleFS, "/reset.css", "max-age=43200");
+  httpServer.serveStatic("/style.css", LittleFS, "/style.css", "max-age=43200");
+  httpServer.begin();
+  webSocket.begin();
+
+  //setEmulation(true);
+}
+
+void loop()
+{
+  if (resetTriggered)
+  {
+    resetTriggered = false;
+    if (DEBUGOUTPUT)
+      Serial.println("RESET");
+  }
+
+  if (loadTriggered)
+  {
+    loadTriggered = false;
+    if (DEBUGOUTPUT)
+      Serial.println("LOAD");
+  }
+
+  if (somthingChanged)
+  {
+    somthingChanged = false;
+    if (DEBUGOUTPUT)
+      Serial.println("UPDATE");
+    webSocket.broadcastTXT("update");
+  }
+
+  if (DEBUGOUTPUT)
+  {
+    Serial.print("Modcounter");
+    Serial.println(moduleCounter);
+
+    Serial.print("Data: ");
+    for (int i = 0; i < MAXMODULECOUNT; i++)
+    {
+      Serial.println(data[i]);
+    }
+  }
+  httpServer.handleClient();
+  webSocket.loop();
+  //runEmulation(); //not working
+
+  if(millis() > 10000 && !interuptsSet)
+  {
+    setupInterupts();
+  }
+}

+ 11 - 0
test/README

@@ -0,0 +1,11 @@
+
+This directory is intended for PlatformIO Unit Testing and project tests.
+
+Unit Testing is a software testing method by which individual units of
+source code, sets of one or more MCU program modules together with associated
+control data, usage procedures, and operating procedures, are tested to
+determine whether they are fit for use. Unit testing finds problems early
+in the development cycle.
+
+More information about PlatformIO Unit Testing:
+- https://docs.platformio.org/page/plus/unit-testing.html