ATTAG Server Box

Alright, forget about the power bank thing I mentioned a couple of days ago, it is pointless. The smartdisplay got an integrated battery management system for a 3.7V lithium cell. So more room means more capacity. I was looking for some pouch cells and found these, which already have the correct connector:

Wiring of the HC-12 antenna.

The bottom of the box got a tab on each side for the usage of a velcro strap, belt or rubber band to place the server on a pole or a tree.

Make sure to add the button before inserting the smartdisplay.

The holes are made for M3 6mm skrews. You need five 6mm in total and two 16mm.

I fixed the battery with double sided tape, same for the HC-12 module and used some two-component glue for the speaker. I guess this could be done a little bit more elegant but anyway. I hope the bending of the antenna doesn’t cause me too much trouble, otherwise I gotta fix that later on. Just try to not bend it that way to avoid communication trouble.

My printer was set a bit too low on Z, so it messed up the logo and the text, but this is some alpha stuff anyway. As you can see it is already running on battery. Double tapping the button switches the server to off.

So here is what you need:

And of course the STL files for printing:

attag server box STL files

OSDYLS power supply for the server

For the server unit I was looking for some small power bank to avoid constructing a battery management and 5V supply. I came across some keychain power banks and picked this one:

It can be opened easily without damaging anything, the inflated pouch pack doesn’t look too trustworthy though. Apart from that the 500mAh should deliver enough power for a 2-3h running time of the server. More than enough time to battle. (From my experience you are really tired after an hour unless your are trained 😉 )

Just need to remove the plug wires and connect to a charging USB socket and solder the 5V to a connector for the display.

OSDYLS – Game master / Host / Server

Since the beginning of the project it was clear that there would be some sort of server, which controls the aspects of the game, like game mode, play time, respawns, ammo and so on. While I was working on the kiln control project I was looking for some tiny touchscreens around and came across these cheap small smart displays on aliexpress. [ link: ESP32-2432S024C ]

Smart Display
Smart Display

Instantly I thought these would be perfect for usage as an OSDYLS host. Though there is a slight drawback, the creators of these displays left very few GPIO pins for external sensors and mods.

However I got myself one of these displays and checked if the HC-12 radio module could be connected to it, and indeed it can. There is also a speaker connector, micro SD slot, WiFi and I think even BT functionality on the board. The vendor provides a pack of examples for it, but I suggest using the [ link: esp32-smartdisplay ] package from github made by [ link: rzeldent ].

You need to get used to visual studio and platformio for it, for a noob like me that was a bit of a challenge but it is quite worth the step. If you are more used to programming with arduino IDE you proably get the libs working there as well of course.

OSDYLS Battle Server

Currently the thing isn’t doing much, I was just creating a simple gui for the beginning using squareline studio, which comes in quite handy for that.

The next step is to create a handshake between players and host, so the server knows who is part of the game. It would automatically fire up the match once all players joined.

Infernomat 1050 – a kiln control Eine Brennofensteuerung

I had this tiny kiln on the shelv for some decades already, my parents gave it to me, when I was a teenager but it never was used due to the lacking temperature control. I only melted some metal with it but never did ceramics. It is an UHLIG U15, I think they are still sold. Max temperature 1050°C, draws 900W, so at 230V little less than 4A.

Now due to 3D printing I thought of some neat ideas that could be done in combination with the kiln. So I deceided to build a control unit for it.

The cheap kiln usually don’t have any control units, with some luck you can buy some random on/off control for them but there is no real temperature control. 

My version got it and you can program different times and temperatures.

You need the following things:

  1. Thermocouple element, I got this one (not yet tested at high-temp) [ link ]
  2. An Arduino UNO or clone
  3. An Elegoo touch display shield [ link ]
  4. A relais which is able to switch with 5V and can handle the amps your kiln is drawing, such as the SLA-05VDC-SL-C
  5. One Adafruit MAX31855 board
  6. Plug, socket and wires for the high voltage stuff
  7. Some PSU for the Arduino (I looted the guts of an old Nokia charger.)
  8. An on/off switch (optional) 
  9. Stuff for soldering, some screws, cable shoes, 3D printer for the housing, all depending on how you are realising it

In order to have some of the GPIOs (I/O pins of the Arduino) left, you need to reduce some functionality of the display, so I cut the pins used for the SD card slot on the display.

Yes I know I could have done better, had no side cutter at hand 😉 These pins are required for the connection of the relais (Arduino GPIO PIN 10) and the thermocouple board (Arduino GPIO PINS 11 to 13)

So the wiring goes as follows:

I soldered the power from the old Nokia PSU to the backside of the Arduino, of course you can also use the power socket or the USB socket of the Arduino. Just a matter of available room. The high voltage power L1 connected to the relais on 1 target at 2, as marked on the picture above. If you choose connector 3 as target, the switching of the relais is inverted.

Now to the code which is doing the magic, I am sorry it is a total mess but I am too lazy to clean it up now. It just grew when I was testing the electronics. Of course it can be made way more elegant. Feel free to do so:

// libs for the display
#include <Elegoo_GFX.h>
#include <Elegoo_TFTLCD.h>
#include <TouchScreen.h>
#include <SPI.h>
#include <Wire.h>

// libs for the sensor
#include "Adafruit_MAX31855.h"

// display stuff, these settings are taken from Elegoo code
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4
#define YP A3
#define XM A2
#define YM 9
#define XP 8
#define TS_MINX 120
#define TS_MAXX 900
#define TS_MINY 70
#define TS_MAXY 920
#define STATUS_X 10
#define STATUS_Y 65

// pin definition for the MAX31855 sensor board, I removed them from the display, where they are used for the SD card
#define MAXDO   12
#define MAXCS   13
#define MAXCLK  11

// define the relais at pin 10 (also removed from the display)
const int relaisIN1 = 10;

// a couple of used variables
unsigned long myTime, newTime, startTime, startTwomin, stopTwomin, newTwomin;

// maxtemp,interval temp, interval time, max time
int parameters[4] = {1050, 100, 60, 30};
int runs = 0;
float rohtemp = 650;
int timepos = 10;
float steptemp = 0;
int looptime = 0;
int oldpos = 0;
int tempcounter = 0;
int simfactor = 1;
int finished = 0;

unsigned long graphcolor = 0xF800;

// #define BLUE    0x001F


float temp = 0;
float tempinc = 10;
float oldtemp = 0;
float deltemp = 0;
float loops = 0;
float numloops = 1;

// initialize the thermocouple sensor
Adafruit_MAX31855 thermocouple(MAXCLK, MAXCS, MAXDO);

// initialize the LCD display and touchscreen
Elegoo_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

// define array of 14 buttons .. yeah I know only 13 are on the screen, too lazy to fix that ;-)
Elegoo_GFX_Button buttons[14];

void setup(void) {
  Serial.begin(9600);
  pinMode(relaisIN1, OUTPUT);
  delay(1000);
  while (!Serial) delay(1); // wait for Serial on Leonardo/Zero, etc
  delay(500);
  Serial.print("Initializing sensor...");
  if (!thermocouple.begin()) {
    Serial.println("ERROR.");
    while (1) delay(10);
  }
  Serial.println("DONE.");
  Serial.println(F("TFT LCD test"));
  Serial.println(graphcolor);

  tft.reset();
  temp = thermocouple.readCelsius();
  uint16_t identifier = 0x9341;
  tft.begin(identifier);
  tft.setRotation(2);
  tft.fillScreen(0x0000);
  tft.setTextSize(2);
  tft.setCursor(10, 2);   tft.print("mx Temp:");
  tft.setCursor(10, 20);  tft.print("iv Temp:");
  tft.setCursor(10, 38);  tft.print("iv Zeit:");
  tft.setCursor(10, 56);  tft.print("mx Zeit:");
  tft.setCursor(10, 153); tft.print("mx Temp:");
  tft.setCursor(10, 187); tft.print("iv Temp:");
  tft.setCursor(10, 222); tft.print("iv Zeit:");
  tft.setCursor(10, 257); tft.print("mx Zeit:");

  tft.setTextColor(0xB596);
  tft.setCursor(110, 2);  tft.print(parameters[0]); tft.print(" 'C");
  tft.setCursor(110, 20); tft.print(parameters[1]);  tft.print(" 'C");
  tft.setCursor(110, 38); tft.print(parameters[2]);  tft.print(" Min");
  tft.setCursor(110, 56); tft.print(parameters[3]);  tft.print(" Min");

  tft.setCursor(0, 0);
  buttons[1].initButton(&tft, 60, 90, 112, 30, 0xFFFF, 0xDDF2, 0xFFFF, "Keramik", 2);
  buttons[2].initButton(&tft, 180, 90, 112, 30, 0xFFFF,  0x94B2, 0xFFFF, "Schmelze", 2);
  buttons[3].initButton(&tft, 60, 125, 112, 30, 0xFFFF, 0xD340, 0xFFFF, "Glasur", 2);
  buttons[4].initButton(&tft, 180, 125, 112, 30, 0xFFFF, 0x8AC8, 0xFFFF, " ", 2);

  buttons[5].initButton(&tft, 150, 160, 53, 30, 0xFFFF, 0x2C5C, 0xFFFF, "-", 2);
  buttons[6].initButton(&tft, 210, 160, 53, 30, 0xFFFF, 0x2C5C, 0xFFFF, "+", 2);

  buttons[7].initButton(&tft, 150, 195, 53, 30, 0xFFFF, 0x2C5C, 0xFFFF, "-", 2);
  buttons[8].initButton(&tft, 210, 195, 53, 30, 0xFFFF, 0x2C5C, 0xFFFF, "+", 2);

  buttons[9].initButton(&tft, 150, 230, 53, 30, 0xFFFF, 0x2C5C, 0xFFFF, "-", 2);
  buttons[10].initButton(&tft, 210, 230, 53, 30, 0xFFFF, 0x2C5C, 0xFFFF, "+", 2);

  buttons[11].initButton(&tft, 150, 265, 53, 30, 0xFFFF, 0x2C5C, 0xFFFF, "-", 2);
  buttons[12].initButton(&tft, 210, 265, 53, 30, 0xFFFF, 0x2C5C, 0xFFFF, "+", 2);
  buttons[13].initButton(&tft, 60, 300, 112, 30, 0xFFFF, 0x0400, 0xFFFF, "START", 2);
  for (uint8_t b = 1; b < 14; b++) {
    buttons[b].drawButton();
  }
  steptemp = parameters[0];
}

#define MINPRESSURE 10
#define MAXPRESSURE 1000

// read temperature from sensor or do uncomment temp=temp+0.001*simfactor; for simulation
void gettemp() {
  temp = thermocouple.readCelsius();
  // temp = temp + 0.001 * simfactor;
}

void relais(int onoff) {
  switch (onoff) {
    case 0:  if (digitalRead(relaisIN1) == HIGH) {
        digitalWrite(relaisIN1, LOW);  // relais1 off
        // lower the temperature for simulation
        Serial.println("off");
        simfactor = -1;
      } break;

    case 1:   if (digitalRead(relaisIN1) == LOW) {
        digitalWrite(relaisIN1, HIGH);  // relais1 on
        // increase the temperature for simulation
        simfactor = +1;
        Serial.println("on");
      } break;
  }
}


void dostop() {  
  relais(0);
  graphcolor = 0x001F;
  tempcounter++;
  if (tempcounter == 1000) {
    tft.setCursor(150, 22); tft.setTextSize(2); tft.setTextColor(0x0000); tft.print(round(deltemp));
    tft.setCursor(150, 22); tft.setTextSize(2); tft.setTextColor(0x07FF); tft.print(round(temp));
    deltemp = temp;
    tempcounter = 0;
  }
  if (newTwomin > (startTwomin + 120000)) {    
    drawtemp();
    startTwomin = newTwomin;
  }
  if (temp < 30) {
    while (1) {}
  }
}

void drawtemp() {
  Serial.println("draw");
  tft.drawLine(timepos, 308 - (round(oldtemp / 5)), timepos + 1, 308 - (round(temp / 5)), graphcolor);
  timepos = timepos + 1;
  oldtemp = temp;
}

void intervall(long laufzeit) {
  newTime = millis();
  // set a start time
  startTime = millis();
  // start the two minute timer for the graph

  // repeat as long as the running time is lower than the interval time laufzeit*60*1000 (minutes to seconds to milliseconds)
  while ((millis() - startTime) < (laufzeit * 60 * 1000)) {
    // Serial.print("L");
    newTwomin = millis();
    gettemp();

    // draw the curve when the timer hits two minutes, then reset the timer
    if (newTwomin > (startTwomin + 120000)) {
      drawtemp();
      startTwomin = newTwomin;
    }

    // if the temperature gets higher than the set temperature shut down the heating
    if (temp > steptemp + 3) {
      relais(0);
    }

    // if the temperature gets too low enable the heating again
    if (temp < steptemp - 3) {
      relais(1);
    }
    printtemp();
  } // while end
  loops = loops + 1;

  // if there is only one heating intervall stop once it is done
  if (parameters[1] == 0) {
    finished = 1;
    timepos = 10;
    relais(0);
  }

  // once reached the biscuit firing go to the final temperature and hold it
  if (steptemp == rohtemp) {
    // heat up to the final temperature
    steptemp = parameters[0];
    // set the heating time for the final heating
    parameters[2] = parameters[3];
  } else {
    // increase the temperature to the next step
    steptemp = (steptemp + parameters[1]);
    relais(1);
  }

  // if temperature is higher than biscuit them set the current target to biscuit temperature
  if (steptemp > rohtemp && steptemp < parameters[0]) {
    steptemp = rohtemp;
  }


  if (temp > parameters[0] - 5) {
    relais(0);
    timepos = 10;
    dostop();
  }


}

// this function sets the presets
void setparameters(int x, int y, int color, int mt, int it, int iz, int mz) {
  tft.setTextColor(0x0000);
  tft.setCursor(x, y);      tft.print(parameters[0]); tft.print(" 'C");
  tft.setCursor(x, y + 18); tft.print(parameters[1]); tft.print(" 'C");
  tft.setCursor(x, y + 36); tft.print(parameters[2]); tft.print(" Min");
  tft.setCursor(x, y + 54); tft.print(parameters[3]); tft.print(" Min");

  parameters[0] = mt;
  parameters[1] = it;
  parameters[2] = iz;
  parameters[3] = mz;

  tft.setTextColor(color);
  tft.setCursor(x, y);      tft.print(mt); tft.print(" 'C");
  tft.setCursor(x, y + 18); tft.print(it); tft.print(" 'C");
  tft.setCursor(x, y + 36); tft.print(iz); tft.print(" Min");
  tft.setCursor(x, y + 54); tft.print(mz); tft.print(" Min");
}

// this function updates the values time and temperature when pressing +/-
void incdec(int x, int y, int color, int orig, int factor) {
  tft.setCursor(x, y); tft.setTextColor(0x0000); tft.print(parameters[orig]); tft.print(" 'C");
  tft.setCursor(x, y); tft.setTextColor(0x0000); tft.print(parameters[orig]); tft.print(" Min");
  newTime = millis();

  // wenn touchscreen länger gedrückt beschleunige auf 10er Schritte, ansonsten +/- 1

  if (newTime - myTime < 160) {
    parameters[orig] = parameters[orig] + 10 * factor;
  } else {
    parameters[orig] = parameters[orig] + 1 * factor;
  }
  if (parameters[orig] < 0) parameters[orig] = 0;


  tft.setCursor(x, y); tft.setTextColor(color); tft.print(parameters[orig]);
  if (orig > 1) {
    tft.print(" Min");
  } else {

    tft.print(" 'C");
  }
  myTime = millis();
}

void printtemp() {
  tempcounter++;
  if (tempcounter == 1000) {
    // print the old temp with black
    tft.setCursor(150, 22); tft.setTextSize(2); tft.setTextColor(0x0000); tft.print(round(deltemp));
    // print the current temp
    tft.setCursor(150, 22); tft.setTextSize(2); tft.setTextColor(0xB596); tft.print(round(temp));
    deltemp = temp;
    tempcounter = 0;
  }
}


void loop(void) {

  // some copied routines from elegoo for the touch control and buttons
  digitalWrite(13, HIGH);
  TSPoint p = ts.getPoint();
  digitalWrite(13, LOW);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);

  if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
    p.x = map(p.x, TS_MINX, TS_MAXX, tft.width(), 0);
    p.y = (tft.height() - map(p.y, TS_MINY, TS_MAXY, tft.height(), 0));
  }

  // go thru all the buttons, checking if they were pressed
  for (uint8_t b = 0; b < 14; b++) {
    if (buttons[b].contains(p.x, p.y)) {
      buttons[b].press(true);  // tell the button it is pressed
    } else {
      buttons[b].press(false);  // tell the button it is NOT pressed
    }
  }

  if (runs == 1) {
    // clear screen
    tft.fillScreen(0x0000);
    // draw grid
    tft.setTextColor(0xEEEE);
    tft.setCursor(2, 22); tft.print("Temperatur:");

    for (uint8_t i = 1; i < 27; i++) {
      tft.drawLine(10, 38 + i * 10, 230, 38 + i * 10, 0x5AEB);
      if ((i - 1) % 3 == 0) {
        tft.drawLine(0 + i * 10, 48, 0 + i * 10, 309, 0x00EB);
      } else {
        tft.drawLine(0 + i * 10, 48, 0 + i * 10, 300, 0x00EB);
      }
      tft.setTextSize(1); tft.setTextColor(0xB596);
      tft.setCursor(0, 30 + i * 10);
      tft.print(1350 - i * 50);
    }


    // draw coords X axis, sadly no linear spacing so one by one
    tft.setCursor(8, 310); tft.print("0");
    tft.setCursor(35, 310); tft.print("60");
    tft.setCursor(62, 310); tft.print("120");
    tft.setCursor(92, 310); tft.print("180");
    tft.setCursor(121, 310); tft.print("240");
    tft.setCursor(152, 310); tft.print("300");
    tft.setCursor(182, 310); tft.print("360");
    tft.setCursor(212, 310); tft.print("420");

    if (parameters[1] > 0) {
      numloops = ceil(rohtemp / parameters[1]);
    }

    // if there are no more heating intervals go to the main temperature, otherwise go to next loop
    if (parameters[1] == 0 && loops < 1) {
      steptemp = parameters[0];
      parameters[2] = parameters[3];
    } else if (parameters[1] > 0 && loops < numloops) {
      steptemp = parameters[1];
    }

    // relais on, start heating
    relais(1);

    // go to the heating loops
    runs = 2;
  }

  if (runs == 2) {
    // since there is no rtc, create a timer
    if (startTime == 0) {
      startTime = millis();
    }
    // zwei Minuten Timer starten
    if (startTwomin == 0) {
      startTwomin = millis();
    }
    // read temperature from sensor (or simulate)
    gettemp();

    // for how long is the program running already?
    newTime = millis();
    newTwomin = millis();

    // draw graph all two minutes
    if (newTwomin > (startTwomin + 120000)) {
      drawtemp();
      startTwomin = newTwomin;
    }

    // if destination temperature reached start the heating loop
    if (temp > steptemp - 1 && parameters[1] > 0 && loops < numloops + 1) {
      intervall(parameters[2]);                                   // loop time
    } else if (temp > parameters[0] - 1 && loops < 1) {
      intervall(parameters[3]);                                   // final time
    }
    // draw cooling graph in blue
    if (numloops == loops) {
      graphcolor = 0x001F;
    }
    printtemp();
  }  else {

    for (uint8_t b = 1; b < 14; b++) {
      if (buttons[b].justReleased()) {
        buttons[b].drawButton();       // unpressed
      }

      if (buttons[b].justPressed()) {
        buttons[b].drawButton(true);  // pressed

        switch (b) {
          // presets
          case 1: setparameters(110, 2, 0xB596, 1050, 100, 60, 30);    break;           // Keramik
          case 2: setparameters(110, 2, 0xB596, 250, 0, 0, 30);        break;           // Schmelze
          case 3: setparameters(110, 2, 0xB596, 1050, 0, 0, 30);       break;           // Glasur
          case 5:  incdec(110, 2, 0xB596, 0, -1);                       break;          // if (parameters[0]<30)   parameters[0]=30;   - maxtemp
          case 6:  incdec(110, 2, 0xB596, 0, 1);                       break;           // if (parameters[0]>1050) parameters[0]=1050; + maxtemp
          case 7:  incdec(110, 20, 0xB596, 1, -1);                      break;          // if (parameters[1]<30)   parameters[1]=30;   - ivtemp
          case 8:  incdec(110, 20, 0xB596, 1, 1);                      break;           // if (parameters[1]>1050) parameters[1]=1050; + ivtemp
          case 9:  incdec(110, 38, 0xB596, 2, -1);                      break;          // - iv Time
          case 10: incdec(110, 38, 0xB596, 2, 1);                      break;           // + iv Time
          case 11: incdec(110, 56, 0xB596, 3, -1);                      break;          // - max Time
          case 12: incdec(110, 56, 0xB596, 3, 1);                      break;           // + max Time
          case 13: runs = 1;                                          break;            // start pressed
        }

        // little delay to prevent uncontrolled ui reactions
        delay(100);
      }
    }
  }

}

If you get some weird results from the thermocouple, make sure you have it wired correctly.    Try switching the cables if the numbers look weird.

There is no ° character in the lib and I am too lazy to embed a new font. 😉

Yes I know the color bleeding in the 3D print sucks. Who cares, doesn’t need to be a beauty.

Questions? Just ask.