📜 ⬆️ ⬇️

Centralized remote control control of lighting sources TsPKIO-2D Rotor

image

Sorry, I have not had fun with naming for a long time, as well as with the periphery for home automation. Specifically, this thing - the remote control light - turned out, because I wanted something with an interface like "click-twist-click", and not with the usual scattering of buttons. The wow effect is not achieved: the home ones do not notice the remote at close range, but at least I have closed the gestalt.

Short TK:

1) Control three lighting groups in the kitchen
2) Control three groups of lighting in the room
3) Control all light sources simultaneously
4) Reasonable battery life (of the week)
5) Compatibility with coding Livolo, SC2260, EV1527
')
So, you don’t need to read further if you don’t like Arduino, Livolo switches and Chinese radio sockets. Because the first is the basis for the console, and the second and third is the periphery.

Concept


The control logic seemed to me the following:

  1. Clicking on the "Krutilka" switches the zones of the lighting group around the ring (the kitchen - room - all).
  2. Turning the knob, depending on the direction of rotation, sequentially turns on or off the lighting of the selected group.
  3. The mode of operation (selected group) is displayed by an unobtrusive LED display.

Since I have radio control on the most despised option, without protection from interference and feedback, a small trick is also provided in case of a miss of a trigger.

If turning the knob does not lead to the desired result, then the combined pressing and turning in the opposite direction allows you to skip the command. Then the command can be repeated as usual.

That is, if I turned the knob clockwise and the main light did not come on, then I can push the knob, turn it counterclockwise, and then release and turn it clockwise again to repeat the switch.


Why so hard? Then, besides awkward protocols, I also have awkward peripherals. For example, Livolo radio-controlled light switches and radio relays, in which the same command to turn on and off, along with the usual radio rosettes, in which the commands to turn on and off are separate.

The trick of skipping a team allows you to creatively beat non-power (non-power), without breaking the general scheme of illumination. In addition, skipping a command allows you to jump over light sources that are not required to be turned on or off.

And, of course, in order to understand what is happening with the console at all, it has a separate indicator, which is lit when a command is sent.

If the console does not touch for some time (configured in the code), then the controller goes to sleep. However, he does not save the last state, and when he wakes up by pressing the handle, he begins his life from scratch.

It's not a mistake. Again, I have switches without feedback, and the console is not physically able to obtain information about the current state of each controlled peripheral device.

Therefore, immediately after waking up, turning the knob begins to either turn on or turn off the light from scratch.

First approach


The visual concept of the “box with a twist” view required, as you might guess, two things: the box and the twist. In the first version, the role of the box was played by a thin powerbank, the use of which solved two problems at once: I had a case and a battery charging circuit, and already with a connector. The battery itself, of course, had to be replaced with a more compact one, otherwise the filling would not fit.

image

With the twister came out intricately. In the course of the search, I found out that the nicer the potentiometer knob and the bigger it is, the closer its gram cost is to the gram gold value. Therefore, I acquired a pen that minimally suited me for aesthetic properties.

image

The control part was the result of an experiment with ATmega328P and a natural continuation of the storyline set by the already existing home automation (on the same Arduino and primitive radio protocols).

I did not buy a lot of the above-mentioned controllers and conditionally layout (in fact, the adapter from the small case to the large step) boards to try to make a low-budget version of the Arduino with the minimum (but reasonable) number of elements.

image

image

image

The experiment turned out to be successful, and the controller configured for the Arduino environment blinked with a LED quite successfully after swallowing the classic Blink. Well, then according to the principle “doris owl”, I added an encoder (with a button), three LEDs, and a conventional transmitter with amplitude modulation on a carrier 433.92 MHz to the resulting board.

To place all the elements in a small case, I had to suffer a little, but the remote still worked. And although it would seem that the problem was solved, I wanted more - the original case.

image

image

Second approach


Actually, the first version (complaining) in appearance was a group of comrades smashed to smithereens, so I postponed it indefinitely. But did not begin to disassemble: sorry.

But when the 3D-printer appeared, he promised himself one day to make the very original case and thus close the issue with the remote control.

I don’t know if it’s good or bad, I don’t really know how to evaluate my pieces. But on 3DToday, the team is more welcoming than on MySKU (which I don’t complain about is not a gift myself), and they rated the body higher than I did.

image

But having complete freedom of action, I refused the frail and vague Chinese batteries, and took the good old 18650 power source. And, as is easy to see, it is its dimensions that largely determine the dimensions of the whole case.

The very same body I began to make modular, consisting of many parts, which allowed to reprint only individual (erroneous or not very optimal) elements, and not the whole product.

Another point is that I really do not like to make cutouts for connectors, which I really do not get. Therefore, in the supply chain there is another trick known for hedgehog Evelyn : wireless charging.



I had another receiver in the storeroom, which I immediately let go of the case.


Finally, the last trick is rather obvious, but still: so that the console does not crawl on the table, I stuck a piece of non-slip car mat to the bottom. And in the end, this thing is an absolute monolith, although rearranging it to another place is also not a problem.

What is required for repetition


Piece of iron


1) ATmega328P controller - 1 pc. (in my TQFP package, but any)
2) 10 kΩ resistor - 5 pcs. (4 for suppressing encoder chatter, 1 for controller)
3) Resistor 100 Ohm - 3 pcs.
4) Ceramic capacitors 0.1 ÎĽF - 4 pcs. (on the controller and encoder chatter suppression)
5) Pressure encoder (valcoder) - 1 pc. (I have a PEC12-4220F-S0024 )
6) LEDs - 3 pcs. (3 mm diameter)
7) Lithium battery charging board - 1 pc. (from the powerbank that came to hand, in theory, any one with automatic activation under load will do)
8) Qi wireless charging receiver - 1 pc.
9) Transmitter with amplitude modulation at 433 MHz - 1 pc. ( like this )
10) Some fiberglass for the encoder board
11) 3D printer
12) Suitable plastic (I printed PLA)
13) M4x30 screws - 4 pcs.

In general, the number of components can be reduced. For example, in a completely minimal version, the controller does not require a strapping at all, although I decided to follow the advice of Nick Gammon and did not regret a pair of capacitors and a resistor.

Similarly, you can not bother with the hardware suppression of contact bounce, and try to do software. Then you can cross out four more resistors and a pair of capacitors.

Alternatively, you can use a ready-made Arduino board, like the Pro Mini, but in this case I cannot guarantee a low level of energy consumption, and you have to conjure it yourself. At the same time it is necessary to correct the case.

Scheme:



For reference, the ATmega328p pinout in the TQFP-32 package from Hobby Electronics :



For my encoder, I drew a small fee:







For good, it would have to be drilled for mounting the encoder, or press it “belly-to-board (taking care of isolation so that there is no short circuit), so that the encoder is mounted a) more or less evenly and b) not loose. Historically, I have the second option.

For the case, it is important that the board height with details, excluding the encoder, be no more (or not much more) than 5 mm.

If you don’t have a ready-made Arduino board, then in order for it to work, you need to first write the Arduino bootloader to the ATmega328P controller.
To do this, you first need to add a description of the controller to the Arduino environment. To do this, go to the official website of Arduino and download from there the archive of descriptions that is appropriate for your version of the environment ( for 1.6 , for 1.5 , for 1.0 ).

The contents of the archive should be extracted to the hardware folder of the Arduino environment folder. In the future, I describe what is happening on the example of the environment 1.0.3, which I still use.

When the descriptions are copied, you should launch Arduino and load the sketch of the programmer into the Arduino, which will be used as the programmer itself. The sketch is in the menu File - Examples - ArduinoISP.

image

Of course, you should choose your fee and port. I choose Mega, because I have it:

image

After downloading the programmer sketch, you need to switch to the target board. Those. in our case, the ATmega328 with a frequency of 8 MHz and an internal master oscillator. It will be on the list of boards if the descriptions mentioned above are copied correctly:

image

Now you need to connect the MISO, MOSI and SCK lines of the programming board and the board with the future Arduino, as well as connect the RESET, GND and VCC. Plus, power is better in the last place.

Based on the above infographics and descriptions of the Arduino Mega, the following picture emerges:

SPI - Arduino Mega - ATmega328p

MISO - 50 - 16
MOSI - 51 - 15
SCK - 52 - 17
SS (RESET) - 53 - 29

Physical connection to your taste, I used exclusively the barbaric method - the usual wire model directly into the holes of the board, without soldering and insulation:

image

If everything is ready - we write down the loader. First, make sure that the correct programmer is selected (Service - Programmer - Arduino as ISP):

image

Then we do the Service - Write the bootloader:

image

After that, the output is a minimalist Arduino board, to load the skits into which you can use a USB-Serial adapter or a full-fledged Arduino board with such an adapter on board. In the first case, you need to cross-connect the RX and TX, and do not forget to connect the common ground. In the second case, you must additionally close the RESET Arduino to earth, which is used as an adapter.

If you, like me, do not have an automatic controller reset circuit before loading the sketch, then there are two options: either pull its reset or just turn on its power when the Arduino environment writes about the start of loading.

Housing


The case, as I said, is modular. This means that the plastic can be thrown onto the inner part hidden from the eyes, which is stale and no longer suitable. You can wear electronics in it:

image

I draw attention to the fact that the body is specific and designed to fit my version of the filling.

I propose to make the rotor transparent so that it disperses the light of the indicators. For greater weight, the M16 nut can be inserted inside the rotor:

image

Still need rotor shirt and cap to it. The lid is simply inserted inside and held on to friction. And, of course, can not do without the top and bottom of the outer case.

I printed the rotor with a filling of 10%, the remaining elements - with a filling of 5%. Plastic - PLA. The set temperature of the nozzle on my printer is 200 on the first three layers, 185 on the following ones. Unfortunately, I can not say what the true temperature of the nozzle. The table is cold.

The assembly is simple.



Boards are placed in the slots of the robust case, LEDs - with legs into the slots of the lower part of the robust case. The transmitter antenna is displayed down, and the wireless charging receiver is also output down so that it is closer to that charge.



The filling is fixed by an intermediate plate, in the groove of which the encoder wire harness passes.

The encoder is fixed with the upper plate, and together it is tightened with screws M4x30, which themselves cut the threads in plastic.

Now the rugged case can be enclosed in halves of the outer case A rotor is put on the encoder shaft, and a shirt is attached to the rotor. Optionally, a non-slip mat is glued to the bottom of the case. Another option is a decorative insert that hides the seam between the body halves.

Code
In the code you need to set commands to turn on and off their peripheral devices. Optionally - change auto-shutdown timeout.

This is all located in the variables section.

//  : http://donalmorrissey.blogspot.ru/2010/04/sleeping-arduino-part-5-wake-up-via.html //  Livolo: http://forum.arduino.cc/index.php?action=dlattach;topic=153525.0;attach=108106 #include <avr/sleep.h> #include <avr/power.h> #include <livolo.h> #define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off) #define adc_enable() (ADCSRA |= (1<<ADEN)) // re-enable ADC #define txPin 7 //   Livolo livolo(txPin); #define PULSESHORT 450 #define PULSELONG 1350 #define PULSESYNC 13950 #define encA 5 #define encB 6 //   #define buttonPin 2 //   () #define roomLed 10 //    #define kitchenLed 9 //    #define switchLed 3 //     #define switchLedTimeOut 150 //      #define switchTreshold 4 //      #define offDelay 15000 //   #define rLev 3 //     #define kLev 3 //     #define glev 2 //     #define txPowerPin 8 //    #define kitchenBackLightOn1 12 #define kitchenBackLightOn2 34 #define kitchenMainLightOn 56 #define roomBackLightOn 12 #define roomMainLightOn1 34 #define roomMainLightOn2 56 #define mainLightOn 12 #define kitchenBackLightOff1 12 #define kitchenBackLightOff2 34 #define kitchenMainLightOff 56 #define roomBackLightOff 12 #define roomMainLighOtff1 34 #define roomMainLightOff2 56 #define mainLightOff 12 #define LivoloID 8500 volatile byte rotorMode = 0; //   byte currentMode = 0; //    int curEncA, prevEncA, curButton, prevButton; //   ,  byte encCountPlus = 0; //   byte encCountMinus = 0; //   unsigned long offTimeOut = 0; //    unsigned long modeTimeOut = 0; //        unsigned long switchLedTime = 0; //      unsigned long modeTime = 0; //   unsigned int modeTreshold = 500; //     (  ) unsigned int bounceTreshold = 200; //     byte rLevState = 0; byte kLevState = 0; byte gLevState = 0; //   k - , r - , h - , b -  boolean kBackState = false; boolean kBackState1 = false; boolean kMainState = false; boolean rBackState = false; boolean rMainState = false; boolean rMainState1 = false; boolean hMainState = false; boolean bMainState = false; boolean afterSleep = false; //      boolean modeTimeOutStart = false; boolean switchLedOn = false; boolean allOn = false; //  " "    boolean allOff = false; //  " "    static void ookPulse(int on, int off) { digitalWrite(txPin, HIGH); delayMicroseconds(on); digitalWrite(txPin, LOW); delayMicroseconds(off); } static void rcSend(long remoteCode) { for (byte reSend = 0; reSend < 8; reSend++) { for(byte repeat=0; repeat<4; repeat++){ for (byte i = 24; i>0; i--) { // transmit remoteID byte txPulse=bitRead(remoteCode, i-1); // read bits from remote ID // Serial.print(txPulse); switch (txPulse) { case 0: // 00 ookPulse(PULSESHORT,PULSELONG); //ookPulse(PULSESHORT,PULSELONG); break; case 1: // 11 ookPulse(PULSELONG,PULSESHORT); //ookPulse(PULSELONG,PULSESHORT); break; } // switch } // for loop ookPulse(PULSESHORT,PULSESYNC); // S(ync) // Serial.println(); } // repeat } delay(150); } void switchLedToggle() { digitalWrite(switchLed, HIGH); switchLedTime = millis(); switchLedOn = true; } void lightsUp(boolean lightsUpMode) { //       ""     //   "" -   if (afterSleep == true) { if (lightsUpMode == false) { gLevState = 1; rLevState = 3; kLevState = 3; } else {gLevState = 0; rLevState = 0; kLevState = 0; } afterSleep = false; //   " " } //   if (rotorMode == 2) { if (lightsUpMode == false){ if (allOff == false) { switchLedToggle(); if (digitalRead(buttonPin) == HIGH) { //   gLevState = 0; rLevState = 0; kLevState = 0; rcSend(kitchenBackLightOff1); rcSend(kitchenBackLightOff2); rcSend(roomBackLightOff); livolo.sendButton(LivoloID, mainLightOff); } allOff = true; allOn = false; } } if (lightsUpMode == true){ //  ,    if (allOn == false) { switchLedToggle(); if (digitalRead(buttonPin) == HIGH) { gLevState = 1; rLevState = 3; kLevState = 3; rcSend(kitchenBackLightOn1); rcSend(kitchenBackLightOn2); rcSend(roomBackLightOn); livolo.sendButton(LivoloID, mainLightOff); //    Livolo livolo.sendButton(LivoloID, kitchenMainLightOn); //#1   livolo.sendButton(LivoloID, roomMainLightOn1); // #2 livolo.sendButton(LivoloID, roomMainLightOn2); // #3 livolo.sendButton(LivoloID, mainLightOn); // #6 } allOn = true; allOff = false; } } } //  if (rotorMode == 1) { if (lightsUpMode == false && kLevState > 0) { switchLedToggle(); if (digitalRead(buttonPin) == HIGH) { if (kLevState == 3) { //   livolo.sendButton(LivoloID, kitchenMainLightOff); // #3 } if (kLevState == 2) { //   2 livolo.sendButton(LivoloID, kitchenBackLightOff2); // #6 } if (kLevState == 1) { //   1 rcSend(kitchenBackLightOff1); } } if (kLevState!=0) { kLevState--;} // Serial.println(kLevState); } if (lightsUpMode == true && kLevState < 3) { switchLedToggle(); kLevState++; if (digitalRead(buttonPin) == HIGH) { if (kLevState > 3) {kLevState = 3;} // Serial.println(kLevState); if (kLevState == 1) { //   1 rcSend(kitchenBackLightOn1); } if (kLevState == 2) { //   2 livolo.sendButton(LivoloID, kitchenBackLightOn2); // #6 } if (kLevState == 3) { //   livolo.sendButton(LivoloID, kitchenMainLightOn); // #3 } } } } //  if (rotorMode == 0) { if (lightsUpMode == false && rLevState > 0) { switchLedToggle(); if (digitalRead(buttonPin) == HIGH) { if (rLevState == 3) { //  1 livolo.sendButton(LivoloID, roomMainLighOtff1); //#1 } if (rLevState == 2) { //   livolo.sendButton(LivoloID, roomMainLightOff2); // #2 } if (rLevState == 1) { //   rcSend(roomBackLightOff); } } if (rLevState != 0) { rLevState--; } } if (lightsUpMode == true && rLevState < 3) { switchLedToggle(); rLevState++; if (digitalRead(buttonPin) == HIGH) { if (rLevState == 1) { //   rcSend(roomBackLightOn); } if (rLevState == 2) { //   livolo.sendButton(LivoloID, roomMainLightOn1); //#1 } if (rLevState == 3) { //   1 livolo.sendButton(LivoloID, roomMainLightOn2); // #2 } } } } } void wakeUp() { detachInterrupt(0); } void setMode() { if (rotorMode >= 2 ) { rotorMode = 0; } else { rotorMode++; } offTimeOut = millis(); } void ledBlink() { for (byte iLed = 0; iLed<3; iLed++) { digitalWrite(kitchenLed, HIGH); digitalWrite(roomLed, HIGH); delay(100); digitalWrite(kitchenLed, LOW); digitalWrite(roomLed, LOW); delay(100); } } void setLed() { if (rotorMode == 0) { //  digitalWrite(roomLed, HIGH); digitalWrite(kitchenLed, LOW); } if (rotorMode == 1) { //  digitalWrite(roomLed, LOW); digitalWrite(kitchenLed, HIGH); } if (rotorMode == 2) { //    digitalWrite(roomLed, HIGH); digitalWrite(kitchenLed, HIGH); } } void enterSleep() { // ledBlink(); afterSleep = true; digitalWrite(txPin, LOW); digitalWrite(txPowerPin, LOW); digitalWrite(roomLed, LOW); digitalWrite(kitchenLed, LOW); digitalWrite(switchLed, LOW); pinMode(txPin, INPUT); pinMode(txPowerPin, INPUT); pinMode(roomLed, INPUT); pinMode(kitchenLed, INPUT); pinMode(switchLed, INPUT); attachInterrupt(0, wakeUp, LOW); adc_disable(); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sleep_mode(); sleep_disable(); power_all_enable(); pinMode(txPin, OUTPUT); pinMode(txPowerPin, OUTPUT); pinMode(roomLed, OUTPUT); pinMode(kitchenLed, OUTPUT); pinMode(switchLed, OUTPUT); digitalWrite(txPin, LOW); digitalWrite(txPowerPin, HIGH); // ledBlink(); setLed(); offTimeOut = millis(); allOn = false; allOff = false; } void setup() { // Serial.begin(115200); pinMode(txPin, OUTPUT); pinMode(txPowerPin, OUTPUT); pinMode(roomLed, OUTPUT); pinMode(kitchenLed, OUTPUT); pinMode(switchLed, OUTPUT); digitalWrite(txPin, LOW); digitalWrite(txPowerPin, HIGH); digitalWrite(roomLed, LOW); digitalWrite(kitchenLed, LOW); digitalWrite(switchLed, LOW); // pinMode(buttonPin, INPUT_PULLUP); pinMode(buttonPin, INPUT); pinMode(encA, INPUT); pinMode(encB, INPUT); prevEncA = digitalRead(encA); offTimeOut = millis(); rotorMode = 0; setLed(); prevButton = digitalRead(buttonPin); } void loop() { if ((millis() - offTimeOut) > offDelay) { enterSleep(); } else { //     if (switchLedOn == true) { if ((millis() - switchLedTime) > switchLedTimeOut) { digitalWrite(switchLed, LOW); switchLedOn = false; } } //       if (digitalRead(buttonPin) == LOW) { offTimeOut = millis(); } //   curButton = digitalRead(buttonPin); if ((prevButton == HIGH) && (curButton == LOW)) { if (modeTimeOutStart == false) { modeTimeOut = millis(); modeTimeOutStart = true; } } else { if (modeTimeOutStart == true) { modeTime = millis() - modeTimeOut; if ((modeTime < modeTreshold) && (modeTime > bounceTreshold)) { setMode(); modeTimeOutStart = false; prevButton = digitalRead(buttonPin); } else { modeTimeOutStart = false; prevButton = digitalRead(buttonPin); } } } //    if (currentMode != rotorMode) { //       ,     currentMode = rotorMode; //     setLed(); } //   curEncA = digitalRead(encA); if ((prevEncA == LOW) && (curEncA == HIGH)) { offTimeOut = millis(); if (digitalRead(encB) == LOW) { encCountMinus++; encCountPlus = 0; // Serial.println("Encoder Minus"); if (encCountMinus > switchTreshold) { encCountMinus = 0; lightsUp(false); } } else { encCountPlus++; encCountMinus = 0; // Serial.println("Encoder Plus"); if (encCountPlus > switchTreshold) { encCountPlus = 0; lightsUp(true); } } } prevEncA = curEncA; } } 


Model housing by reference .

Everything.

PS: I tried not to forget anything, but I could. If so, I apologize and I will do my best to answer the leading questions correctly and correct mistakes.

Source: https://habr.com/ru/post/411093/


All Articles