Steeph's Web Site

Menu

Entries tagged 'cat:Electronics'

Poly Keyboard

Here is another project idea I never really started working on: A computer keyboard that has a small LCD in each key cap. I'm convinced that there are legitimate use cases. After all, the function of the keys changes according to context. Different applications have different shortcuts, when I press and hold the Ctrl or Super key, the whole layout practically changes. Most people don't remember useful keyboard shortcuts, if they try to memorise them in the first place (if the learn about them to begin with). It would be nice to have the markings on the keys reflect their role. In some special applications, like video editing, a completely custom keyboard layout would be useful (a cheap alternative to byuing a custom video editing interface input device). In computer games, only the useful keys could light up, as well as display what they do (which depends not only on the game, your custom layout, the current situation in the game, as well as what happened earlier in the game, e.g. what items you have in your pockets). You might want to write in different languages and need different keyboard layouts at different times. You could have icons or descriptions of key functions be displayed whenever you hold a modifying key, to see which shortcuts are available. You could have a second keyboard with an additional layout to give you access to frequently used functions, and have the keys display the relative application icons or describe what they do. And it would just look cool.

The idea has been in my head for years. I even board the electronics to build a few keys to try it out. But I never build one because I think it would have been too much work on the side to get it done well, considered I come by very well without one. Now I learned that somebody else has built a keyboard very similar to what I had/have in mind. To be honest, I never really get very deep into the keyboard building community to know whether a keyboard like this already existed. I think when I had the idea I didn't even know how big the mechanical keyboard fan community is and that DIY builds are such a big thing.

The project I stumbled over is the Poly Keyboard by thpoll. Here is an article about it in the Keyboard Builder's Digest, here is the Github repo. Apparently (according too the article) it isn't the first of its kind. But it's the only one I've seen.

PIN Changer

I had to change the default PINs of over 200 SIM cards once. And such a situation could arise again. So I've built a PIN changer in which I just have to insert the card, wait a few seconds and it's done.

The Card Slots

SIM cards in their natural form factor aren't as fiddly to handle as they are in the form factors most people know, which is Mini SIM, Micro SIM and Nano SIM. Classical SIM cards are the same size as other smart cards. I found a card slot with an end switch on eBay. I like it when I find industry grade parts for cheap on eBay as part of some remaining stock. Additionally I've used a slide-in mini SIM slot and another, separate end switch from my parts collection in case I to change the PINs of smaller cards.

The Baseband Processor

Other parts that I've used is an Arduino Nano sized Arduino Nano nearly-clone and an A6 modem module. There are many similar modem modules designed around different but similar ICs. Many of them are cheap and widely used for DIY IoT projects. So example code for the Arduino and other help can be easily found on the web. I don't know why I went with a module with an A6. But it works fine and there are an Arduino library for it as well as cheap modem modules with it available.

(tba:voltage supply)

The Controller

Yes, Arduino may be kind of the noob go-to board and could look up how to use microcontrollers on their own finally and even if I don't want to I could finally start to use ESP32s like everybody else. But I know Arduinos and by now I'm familiar with it and it works, so, whatever. Arduino Nano is kind of my go-to form factor now because they have integraded USB, are Uno compatible and small. Unless I need more or something very specific I use Arduino Nano almost-clones with USB-C port.

The Code

The code is a real mess. It had been a long time since I had written any even halfway serious C. It may have been the first time, actually. The sketch surely is very easily improved by somebody who knows what they are doing. I intended to improve it myself. But the project is currently abandoned and The code is doing what it should in a way I initially had in mind as the goal. But I'll leave the mess of the comments in for the case that somebody wants to make out what I was thinking.


// Funktionen umschreiben: Beim Empfangen wird erwartet: 1. der AT-Command zurück, 2. eine Antwort, 3. OK oder ein ERROR.
//                         Daher sollte abgefragt werden, bis entweder OK oder ERROR kam oder 20/50/9001(?) Abfragen lang weder OK noch ERROR an kam.
//                         Die Antwort in Variable speichern? Naja, String zurückgeben halt.
//                         Antworten, die mit "^" anfangen brauchen nicht gehandlet zu werden, da keine Kommandos, die mit AT^ beginnen gesendet werden.


#include  

SoftwareSerial A6MODULE(6,7);
int intled = 13; // Internal LED
int successled = 8; // Green LED
int failled = 9; // Red LED
int wrongpinled = 10; // Orange LED

boolean debug=true;

//String commands[5] = { "AT", "AT+CPINC2", "AT+CPIN?", "AT+CLCK=\"SC\",2", "AT+CPIN=\"3010\"" };
//int command = 0;

const byte maxmsglength = 32;
char received[maxmsglength];
boolean newData = false;
String response = "";

int i=0;

/*
To check/do:
1  AT: OK?
2  is PUK required - abort
3  are less than 3 PIN attempts left? - abort AT^CPINC=?
4  is PIN disabled
       5  enable it: 0000
6  is PIN enabled
       7  unlock
8  is card unlocked
       9  change PIN: 1996
              was PIN wrong - report and leave it
*/


void setup() {
  pinMode(intled, OUTPUT);
  pinMode(successled, OUTPUT);
  pinMode(failled, OUTPUT);
  pinMode(wrongpinled, OUTPUT);
  // All LEDs turn on at the beginning and stay on during the wait period at the beginning, then turn off before communication with the A6 module starts.
  digitalWrite(intled, HIGH);
  digitalWrite(successled, HIGH);
  digitalWrite(failled, HIGH);
  digitalWrite(wrongpinled, HIGH);
  Serial.begin(9600);
  delay(500);
  digitalWrite(intled, LOW);
  digitalWrite(successled, LOW);
  digitalWrite(failled, LOW);
  digitalWrite(wrongpinled, LOW);
  A6MODULE.begin(9600);
  delay(500);
  digitalWrite(intled, LOW);
}

void loop() {
  sendtoa6("AT");
  if(getfroma6("OK")) {
//    sendtoa6("AT+CPIN?");
//    if(getfroma6("+CPIN:SIM PUK")) { fail; }         // If the required password is PUK, abort.
//    sendtoa6("AT+CPIN?");
//    if(getfroma6("+CPIN:SIM PIN2")) { fail; }        // If the required password is PIN2, abort.
//    sendtoa6("AT+CPIN?");
//    if(getfroma6("+CPIN:SIM PUK2")) { fail; }        // If the required password is PUK2, abort.

// WAIT FOR SWITCH RELEASE FIRST
    sendtoa6("AT+CPIN?");
    getfroma6("+CPIN:SIM PIN");                      // The last non-empty response will be stored in the global response variable. Problem with this: If the A6 module sends an unsolicitated message before the response to the CPIN command, nothing gets done and the card needs to be re-inserted again.
    if(strcmp(response, "OK") == 0) {                // If already no PIN is required

//      sendtoa6("AT+CLCK=\"SC\",2"); // Ist PIN-Abfrage eingeschaltet? Oder ist es "SC"?
//      if(getfroma6(???)) { PIN-Abfrage einschalten mit 0000; }

      // ENABLE PIN HERE
      d("I don't know how to enable the PIN.");

    }
    if(response = "+CPIN:SIM PIN") {                 // If the required password is PIN, continue.
      sendtoa6("AT^CPINC=?");
      if(getfroma6("^CPINC: 3")) {                   // If not exactly 3 attempts are left, abort. (should be larger than or equal to 3, shouldn't it?)
        sendtoa6("AT+CPIN=\"0000\"");                // Freischalten mit 0000
        delay(50);
        if(!getfroma6("OK")) { fail; }               // If that was not the right password, abort.
        sendtoa6("AT+CPWD=\"SC\",\"0000\",\"1996\"");// PIN ändern
        delay(500);
        if(getfroma6("OK")) { Serial.println("Looking good."); }

//      sendtoa6("AT+CMGD=0,4"); // Should delete all SMS
//      if(getfroma6(???)) { ; }
        d("I don't know how to delete SMS.");

        sendtoa6("AT+CPIN?");
        if(getfroma6("+CPIN:READY")) {
          
          // TURN OFF A& MODULE FOR SAFE CARD REMOVAL

          digitalWrite(successled, HIGH);
        }
      } else {                                         // If not exactly 3 times left
        fail;
      }
      delay(1000);
    }
  }
}

void fail() {
  digitalWrite(failled, HIGH);               // Turn red fail LED on and ...
  d("Something failed! Ending programme.");
  while(1);                                  // ... don't do anything anymore.
}

void wrongpin() {
  digitalWrite(wrongpinled, HIGH);           // Turn yellow LED on and ...
  d("Wrong PIN! Ending programme.");
  while(1);                                  // ... don't do anything anymore.
}

//boolean getfroma6(char str[32], char str1[32], char str1[32], char str1[32], char str1[32], char str1[32]) { // Returns true if the passed (expected) message was received, false if anything else was received.
boolean getfroma6(char str[32]) { // Returns true if the passed (expected) message was received, false if anything else was received.
  boolean asexpected = false;
    for (i = 1; i < 9; ++i) {
    receivelinefroma6();
//    d("d1 "+response);

    if(received[0] == '\0') {                            // If the received message is empty
      continue;
    } else {
      response = received;
    }
    if(strstr(received, "ERROR") != NULL) {              // If the received message contains "ERROR"
      d("Received an error: "+response);
      fail;
    }
    if(received[0] == '+') {                             // If the received message starts with a "+" sign
      d("Reseived response: "+response);
    } else {
      if(strstr(received, "OK") != NULL) {               // If the received message is "OK"
        d("Received OK.");
        asexpected = true;                               // Also treat OK like the expected message. No unexpected OK should ever be sent from the A6 module. So this is fine. No, it is, really.
//        if(asexpected) { return true; }
      } else {
        if(strncmp(received,"AT",2) == 0) {              // If the received message starts with "AT"
          d("Received AT: "+response);
        } else {                                         // For any other received message
          d("Received: "+response);
        }
      }
    }
    newData = false;
    if(strstr(received, str) != NULL) {
      d("Received expected message: "+response);
      asexpected = true;
    }
  }
  if(asexpected) { return true; }
  return false;
}


//void handleresponse() {
//  response = received;
//  if(received[0] == '+') {
//    d("               Response: "+response);
//  } else {
//    if(strstr(received, "OK") != NULL) {
//      d("               It's okay.");
//    } else {
//      if(strncmp(received,"AT",2) == 0) {
//        d("               I've sent: "+response);
//      }
//    }
//  }
//  newData = false;
//}



void receivelinefroma6() {
  delay(80);
  static byte counter = 0;
  char rc;
  
  received[0] = '\0';

  while (A6MODULE.available() > 0 && newData == false) {
    rc = A6MODULE.read();

    if (rc != '\n') {
      received[counter] = rc;
      counter++;
      if (counter >= maxmsglength) {
          counter = maxmsglength - 1;
      }
    }
    else {
      received[counter] = '\0'; // terminate the string
      counter = 0;
      newData = true;
    }
  }
}

void sendtoa6(String command) {
//  Serial.println(command);
  A6MODULE.println(command);
  d("Sent: "+command);
}

void d(String line) {
  if (debug == true) { Serial.println(line); }
}

(tba:connections,assembly,photos?)

All Those Bike Attachements

I think bicycle accessaries are of these things that developed over time past the point where it would have been sensible to rethink how things are done. I think it would make sense the rethink how all those bike attachments are integrated.

A bicycle without any attachments is already nice. It can be used without them and none of the things are necessary all the time. So it can make sense to have them detachable. But probably most people use their bike mainly for one thing - transportation, mountain-biking, sports - and need the same combination of attachments most times the bike is used. (Minus the lights when it's not dark.) I think it's rather peculiar that people buy, attach and use all these extras without questioning the crowded state of their bicycle frame. Let me list the things that I find useful myself.

  • Bell (*)
  • Front light (*)
  • Back light (*)
  • Phone/GPS holder
  • Phone charger
  • Action camera
  • Remote for indicators (in helmet or jacket/shirt)
  • Horn

(*): required by law when driving on the street

So, in this configuration, when you get back from riding your bike, you have to turn off the front light, turn off the back light, turn off the helmet indicator, turn off the indicator remote, check the charge of the front light, the back light, the phone charger, the GPS, the helmet, the helmet control, the horn and the action cam, detach the things that need charging, bring them in the house and plug them in for charging one after another. You may also have an electronic lock, additional front lights, a breaking light, the remote control for the breaking light and a headlamp. Half of them still have a micro USB port and none of the batteries last for dozens of rides (unless you ride mostly when it isn't dark). Sure, that comes with the benefit of having these devices, people seem to think. But they don't do all of this when they get out of their car. Because it wouldn't make sense to have a separate battery for every electronic device in a modern car. Neither does it make sense on a bike.

It would be nice to have a central battery for everything on my bike and a single on/off switch. But I don't use it enough to see me investing in building it myself. Because if I'd do it, I'd want it to be safe and secure in any weather, look good and not be too clunky or heavy. So I'd have to invest time.

Zeo Mobile Teardown

2015 habe ich mir mal ein Zeo Mobile genauer angeschaut und im Schlafhacking-Blog darüber geschrieben.

Hintergrund: Das Zeo war ein Gerät ("war, weil es schon lange nicht mehr hergestellt und vertrieben wird), das anhand von Bewegungsdaten, EOG und EEG ein Schlafprofil des Trägers eines Stirnbandes erstellte. Es war wahrscheinlich das erste Gerät seiner Art, das diesen Einsatzzweck einfach benutzbar, mit cloudbasierter Software für Normalconsumer und - dafür, dass es quasi kein richtiges Konkurrenzprodukt gab - ziemlich günstig, bedient hat. Ich weiß gar nicht, woran der Hersteller, Zeo, Inc., ehem. Axon Labs) gescheitert ist. Er war dem heutigen Trend so weit voraus und hat alles geboten, was den Benutzern von heutigen, ähnlichen Geräten für den Zweck der Verbesserung der Schlafqualität wichtig ist, dass man ihn eher als Innovator als als seiner Zeit voraus bezeichnen kann. Anyways, es gab/gibt hauptsächlich zwei Geräte: Das Zeo Bedside, bestehend aus Nachttisch-Empfangsgerät, Kopfband, USB-/serieller Schnittstelle und Windows-Software, und das Zeo Mobile, ein Bluetooth-fähiges Kopfband mit Smartphone-App. Für ersteres wurde von einem (Ex-)Entwickler des Gerätes eine modifizierte Firmware (Version 2.6.3R) in Umlauf gebracht, die es ermöglicht, über USB die Rohdaten der Sensoren zu empfangen und so eigene Software zu benutzen. Damit hat während des kommerziellen Untergangs Zeos nicht nur er meinen Respekt gewonnen, sondern auch das Zeo Bedside neue Einsatzmöglichkeiten und eine Fan-Community von technik-interessierten Klarträumern. Für das neuere Zeo Mobile gibt es zwar auch eine Android-App, mit der man das Gerät ohne Konto auf dem nicht mehr existeierenden Zeo-Server weiterbenutzen kann, aber aus dem einfachen Grund, dass es nicht einfach möglich ist, Rohdaten aus dem Gerät zu bekommen, haben die Zeo Mobiles bei weitem nicht die Beliebtheit unter Hobbyisten, die die wenigen noch in Umlauf befindlichen Zeo Bedsides genießen.

Also, 2015 habe ich mir mal ein Zeo Mobile genauer angeschaut und im Schlafhacking-Blog darüber geschrieben: Zeo Mobile Teardown

Eigentlich wollte ich noch einige weitere Links hier einbauen, wie zum Beispiel zu der Entwickler-Webseite und zu ein paar interessanten Software-Projekten, die die Daten der Rohdaten-Firmware des Zeo Bedside nutzen. Leider kann ich die Firmware selbst nicht mehr finden und auch die relevanten Informationen sind nur noch im Internet Archive zu finden. Dort sind die Software-Downloads aber nicht archiviert, weshalb sich der Rest erübrigt. Sehr schade. Ich hoffe jemand wird die Sachen noch mal öffentlich zur Verfügung stellen. Ich habe sie leider nicht bei mir, weil ich nie ein Zeo selbst bespielt habe.

Vielleicht nehme ich den letzten Absatz wieder zurück. Hier ein paar URLs, die oben vielleicht fehlen (habe gerade keine Lust, sie in line zu bringen):

  • https://www.gwern.net/docs/zeo/index
  • https://www.gwern.net/docs/zeo/firmware-v2.6.3R-zeo.img
  • https://www.openyou.org/2013/06/11/zeo-firmware-and-raw-data-api-on-openyou-github/
  • http://eric-blue.com/2013/06/09/life-beyond-zeo/
  • https://github.com/evsc/zeoLibrary
  • https://github.com/openyou/zeo-firmware
  • https://github.com/openyou/zeo-raw-data-api
  • https://github.com/openyou/zeo-android-api
  • https://www.klartraumforum.de/forum/showthread.php?tid=9523
  • http://www.sleepstreamonline.com/rdl/
  • https://web.archive.org/web/20120422001938/http://sourceforge.net/projects/zeorawdata
  • https://web.archive.org/web/20110307193953/http://sourceforge.net/projects/zeodecoderview
  • https://sourceforge.net/projects/rxtxlibrary/files/rxtxlibrary/
  • https://sourceforge.net/projects/zeolab/
  • https://sourceforge.net/projects/zeolib/
  • https://web.archive.org/web/20120924070141/http://developers.myzeo.com/raw-data-library/
  • https://www.klartraumforum.de/forum/forumdisplay.php?fid=29 (Inhalt nur eingeloggt sichtbar)

USB/Serial PWM Fan Controller Using an Arduino

I wanted to be able to control the speed of the fans in my big NAS, Fred, individually. Even though the mainboard in use has five PWM fan connectors, the chipset can only control the speed of all fans together. There are probably good fan controllers commercially available that solve this problem better than I did. But they seemed overpriced and it seemed like a fun learning project for me.

The fan controller that I made uses an Arduino Nano clone that listens to it's serial port, waiting for a command to change the speed of a fan. When a command is recognised the continuous PWM signal for that fan is changed accordingly. It's possible to control up to six fans this way with an Arduino Nano. I'm only using three though since I only have three fan groups that need to be controlled separately.

The Arduino sketch/C code for the Arduino Nano that I used is as follows.




//fan speed sensor wire attached to digital pin 2 with a 10kohm pullup resistor
//fan PWM control wire attached directly to digital pin 9

#include <PWM.h> //include PWM library http://forum.arduino.cc/index.php?topic=117425.0

volatile int half_revolutions1; //allow half_revolutioins to be accesed in intterupt
volatile int half_revolutions2; //allow half_revolutioins to be accesed in intterupt
int rpm1; //set rpm as an integer
int rpm2; //set rpm as an integer
int pwm=255;
const byte numChars = 5;
char receivedChars[numChars];

boolean newData = false;

void setup()
{
  InitTimersSafe(); //not sure what this is for, but I think i need it for PWM control?
  bool success = SetPinFrequencySafe(9, 25000); //set frequency to 25kHz
  pwmWrite(9, 51); // 51=20% duty cycle, 255=100% duty cycle

  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  analogWrite(5, 170);
  analogWrite(6, 255);
  pinMode(2,INPUT_PULLUP); //set RPM pin to digital input
  pinMode(3,INPUT_PULLUP); //set RPM pin to digital input
  half_revolutions1 = 0;
  rpm1 = 0;
  half_revolutions2 = 0;
  rpm2 = 0;

  Serial.begin(9600);
}



void loop()
{
  sei(); //enable intterupts
  attachInterrupt(0, fan_rpm1, RISING); //record pulses as they rise
  delay(1000);
  detachInterrupt(0);
  attachInterrupt(1, fan_rpm2, RISING); //record pulses as they rise
  delay(1000);
  detachInterrupt(1);
  cli(); //disable intterupts

  rpm1 = (half_revolutions1/2)*60;

  Serial.print("1");
  Serial.println(rpm1);

  rpm2 = (half_revolutions2/2)*60;

  Serial.print("2");
  Serial.println(rpm2);

  rpm1 = 0;
  half_revolutions1 = 0;

  rpm2 = 0;
  half_revolutions2 = 0;

  pwm = 255;
  recvWithStartEndMarkers();
  processCommand();
}

void fan_rpm1()
{
  ++half_revolutions1; //increment before returning value
}


void fan_rpm2()
{
  ++half_revolutions2; //increment before returning value
}


void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = 's';
    char endMarker = '\n';
    char rc;
 
    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

void processCommand() {
    if (newData == true) {
        Serial.print("s");
        Serial.println(receivedChars);
        switch (receivedChars[0])
        {
            case '1':
                receivedChars[0] = '0';
                sscanf(receivedChars, "%d", &pwm);
                analogWrite(5, pwm);
                break;
            case '2':
                receivedChars[0] = '0';
                sscanf(receivedChars, "%d", &pwm);
                analogWrite(6, pwm);
                break;
            case '3':
                receivedChars[0] = '0';
                sscanf(receivedChars, "%d", &pwm);
                
                break;
//            default:
//                Serial.println("I don't know what that means.");
        }
        newData = false;
    }
}

Well, how should I put it? It works, usually.

(tbc?)

(tba:photos)