commit 33e0f703fe80669439fae2982ba3f9b2e556ffd5 Author: Dustin Brunner <75931548+dustinbrun@users.noreply.github.com> Date: Sun Sep 11 11:41:11 2022 +0200 V1 diff --git a/Motor_Driver_Board_TLE4202B.fzz b/Motor_Driver_Board_TLE4202B.fzz new file mode 100644 index 0000000..01b9910 Binary files /dev/null and b/Motor_Driver_Board_TLE4202B.fzz differ diff --git a/Motor_Winch_Drum.stl b/Motor_Winch_Drum.stl new file mode 100644 index 0000000..8f204f2 Binary files /dev/null and b/Motor_Winch_Drum.stl differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..f32bddf --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# Arduino electric chicken stall door +Diy motorized door for a chicken stall, controlled by Arduino Nano + +# Features +- motor can be controlled with + - buttons on the main controller box + - 433 Mhz remote + - UART (RX/TX) communication (can be used with a PC or with a ESP8266 to make the system controllable over Wifi) +- two Reed-switches to indicate top and bottom position of the door +- safety end-stop-switch to cut off the power if the door in case of failure does not stop in its top position +- Solenoid lock to lock the door +- leds to indicate error/ready state + + + + + +# Hardware + +## Parts-List +- Motor with gear-system to create some holding torque in the top position (and stop the door from going down again by itself). + I used one with 20 rpm and a torque of 0,15Nm ([link](https://www.pollin.de/p/gleichstrom-getriebemotor-pgm-37dc12-21-310482)) +- HG7881 motor driver board, I used this one ([link](https://www.pollin.de/p/motorantriebsmodul-daypower-motor-9110-810572)) + + OR: A [TLE4202B](https://www.pollin.de/p/tle4202b-sts-b57928-101044) and a N-Channel Mosfet to create your own motor- and solenoid-driver board (see below) +- 2x Some kind of door reed contact ([link](https://www.pollin.de/p/tuerkontakt-fensterkontakt-mit-25-cm-anschlusslitzen-10-w-braun-580569)) +- 433 Mhz receiver ([link](https://www.amazon.de/iHaosapce-Wireless-Transmitter-Receiver-Raspberry/dp/B07B9KV8D9/ref=sr_1_5?__mk_de_DE=%C3%85M%C3%85%C5%BD%C3%95%C3%91&crid=QDNC0EBPSCZW&keywords=receiver+433+mhz&qid=1662887769&sprefix=receiver+433+mh%2Caps%2C147&sr=8-5)) + + + +- Solenoid Lock ([link](https://www.ebay.de/itm/273873875970), 12V/350mA version) +- Some leds, resistors, screw terminals, ... + +## Schematic + + + +## PCB Layout + + + +## Front panel for the main controller +The size of this frontpanel is 125x70mm. You can customize it using the free ["Frontplatten Designer"](https://www.schaeffer-ag.de/frontplatten-designer). + + + +# Motor Driver +You can either use a premade motor driver like the one I linked in the parts list or build your own. The pins on the main pcb for this driver stay the same. +This driver is based on the TLE4202B. Since the solenoid lock doesn’t need a in the polarity switchable voltage, we can use a simple Mosfet to control it. + + + + + +This circuit is not perfect, since it does not feature any filter-capacitors, which are recommended in the datasheet. But it works for the most parts :). + + +# 3D-printed parts +## Winch drum for the motor +I created a 3D-model for a winch drum, to which rope of the door the can be spooled to. It fits to the flattened motor axis. A M3 screw can be screwed in the second hole to tighten the motor axis. The rope can also be tightened to this screw (if the screw is long enough). This part fits to the motor linked in the parts list and I think it will also fit to any other motor with 6mm axis. +You can customize the model using tinkercad and [this link](https://www.tinkercad.com/things/7C84V8nsqCm). +### [3D-Model](Motor_Winch_Drum.stl) + + +## Mount for the solenoid lock +This part is used to hold the solenoid lock. It will fit to the solenoid linked in the parts list. For other solenoids please check the dimensions. You can customize the model using tinkercad and [this link](https://www.tinkercad.com/things/a6mTNkn8J4k). +### [3D-Model](Solenoid_Lock_Mount.stl) + + + + + +# Software +In the code I am using the RCSwitch-library. Since it is already included in the code-folder there is no need to download an import it manually. I have linked it in the code anyway. + +To get your 433Mhz remote working with the controller fist upload the `ReceiveDemo_Simple.ino`-sketch from the RCSwitch-Library examples. Now open the serial monitor an then press the keys on your remote, that you want to use. Now copy the received codes from the serial monitor into the `Remote Codes`-Section in the code. + +You need to test out how long your opening/closing process of the door takes with your motor. If you have measured it add a few seconds to the value an change the `motor_timeout`-variable in the code to this value in **milliseconds** (10s = 10000ms). If the motor is running longer than this timeout it stops automatically. + + +## Serial Communication +I implemented a serial interface to control the movement. If you add a Wifi-capable microcontroller (like the ESP8266) to this interface, it can make the system remote-controllable over wifi. + +The functionality of the UART interface: +- Baudrate: **9600** +Commands: +- Request State of the Door: 'S' + - Returns: 1 - Door is open, 0 - Door is closed, 2 - Unknown Position +- Open Door: 'U' + - Returns: 'U' + - If it fails or not possible (already open): 'E' +- Open Door: 'D' + - Returns: 'D' + - If it fails or not possible (already closed): 'E' +- Stop Movement: 'H' + - Returns: 'H' + + +# References/Sources +- RC Switch Lib: https://github.com/sui77/rc-switch +- CCC - Chicken-Coop-Control: https://sites.google.com/view/arduino-chicken-coop-control/home +- https://funduino.de/nr-03-433mhz-funkverbindung + + +
+

This work by Dustin Brunner is licensed under CC BY 4.0

+ +Creative Commons Lizenzvertrag
Dieses Werk von Dustin Brunner ist lizenziert unter einer Creative Commons Namensnennung 4.0 International Lizenz. + diff --git a/Solenoid_Lock_Mount.stl b/Solenoid_Lock_Mount.stl new file mode 100644 index 0000000..defdbfc Binary files /dev/null and b/Solenoid_Lock_Mount.stl differ diff --git a/chicken_stall_door_frontpanel.fpd b/chicken_stall_door_frontpanel.fpd new file mode 100644 index 0000000..b512140 Binary files /dev/null and b/chicken_stall_door_frontpanel.fpd differ diff --git a/chicken_stall_door_schematic.fzz b/chicken_stall_door_schematic.fzz new file mode 100644 index 0000000..2fb2091 Binary files /dev/null and b/chicken_stall_door_schematic.fzz differ diff --git a/chicken_stall_door_software/RCSwitch.cpp b/chicken_stall_door_software/RCSwitch.cpp new file mode 100644 index 0000000..0d9a1ae --- /dev/null +++ b/chicken_stall_door_software/RCSwitch.cpp @@ -0,0 +1,697 @@ +/* + RCSwitch - Arduino libary for remote control outlet switches + Copyright (c) 2011 Suat Özgür. All right reserved. + + Contributors: + - Andre Koehler / info(at)tomate-online(dot)de + - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com + - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46 + - Dominik Fischer / dom_fischer(at)web(dot)de + - Frank Oltmanns / .(at)gmail(dot)com + - Andreas Steinel / A.(at)gmail(dot)com + - Max Horn / max(at)quendi(dot)de + - Robert ter Vehn / .(at)gmail(dot)com + - Johann Richard / .(at)gmail(dot)com + - Vlad Gheorghe / .(at)gmail(dot)com https://github.com/vgheo + + Project home: https://github.com/sui77/rc-switch/ + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "RCSwitch.h" + +#ifdef RaspberryPi + // PROGMEM and _P functions are for AVR based microprocessors, + // so we must normalize these for the ARM processor: + #define PROGMEM + #define memcpy_P(dest, src, num) memcpy((dest), (src), (num)) +#endif + +#ifdef ESP8266 + // interrupt handler and related code must be in RAM on ESP8266, + // according to issue #46. + #define RECEIVE_ATTR ICACHE_RAM_ATTR +#else + #define RECEIVE_ATTR +#endif + + +/* Format for protocol definitions: + * {pulselength, Sync bit, "0" bit, "1" bit} + * + * pulselength: pulse length in microseconds, e.g. 350 + * Sync bit: {1, 31} means 1 high pulse and 31 low pulses + * (perceived as a 31*pulselength long pulse, total length of sync bit is + * 32*pulselength microseconds), i.e: + * _ + * | |_______________________________ (don't count the vertical bars) + * "0" bit: waveform for a data bit of value "0", {1, 3} means 1 high pulse + * and 3 low pulses, total length (1+3)*pulselength, i.e: + * _ + * | |___ + * "1" bit: waveform for a data bit of value "1", e.g. {3,1}: + * ___ + * | |_ + * + * These are combined to form Tri-State bits when sending or receiving codes. + */ +#ifdef ESP8266 +static const RCSwitch::Protocol proto[] = { +#else +static const RCSwitch::Protocol PROGMEM proto[] = { +#endif + { 350, { 1, 31 }, { 1, 3 }, { 3, 1 }, false }, // protocol 1 + { 650, { 1, 10 }, { 1, 2 }, { 2, 1 }, false }, // protocol 2 + { 100, { 30, 71 }, { 4, 11 }, { 9, 6 }, false }, // protocol 3 + { 380, { 1, 6 }, { 1, 3 }, { 3, 1 }, false }, // protocol 4 + { 500, { 6, 14 }, { 1, 2 }, { 2, 1 }, false }, // protocol 5 + { 450, { 23, 1 }, { 1, 2 }, { 2, 1 }, true } // protocol 6 (HT6P20B) +}; + +enum { + numProto = sizeof(proto) / sizeof(proto[0]) +}; + +#if not defined( RCSwitchDisableReceiving ) +volatile unsigned long RCSwitch::nReceivedValue = 0; +volatile unsigned int RCSwitch::nReceivedBitlength = 0; +volatile unsigned int RCSwitch::nReceivedDelay = 0; +volatile unsigned int RCSwitch::nReceivedProtocol = 0; +int RCSwitch::nReceiveTolerance = 60; +const unsigned int RCSwitch::nSeparationLimit = 4300; +// separationLimit: minimum microseconds between received codes, closer codes are ignored. +// according to discussion on issue #14 it might be more suitable to set the separation +// limit to the same time as the 'low' part of the sync signal for the current protocol. +unsigned int RCSwitch::timings[RCSWITCH_MAX_CHANGES]; +#endif + +RCSwitch::RCSwitch() { + this->nTransmitterPin = -1; + this->setRepeatTransmit(10); + this->setProtocol(1); + #if not defined( RCSwitchDisableReceiving ) + this->nReceiverInterrupt = -1; + this->setReceiveTolerance(60); + RCSwitch::nReceivedValue = 0; + #endif +} + +/** + * Sets the protocol to send. + */ +void RCSwitch::setProtocol(Protocol protocol) { + this->protocol = protocol; +} + +/** + * Sets the protocol to send, from a list of predefined protocols + */ +void RCSwitch::setProtocol(int nProtocol) { + if (nProtocol < 1 || nProtocol > numProto) { + nProtocol = 1; // TODO: trigger an error, e.g. "bad protocol" ??? + } +#ifdef ESP8266 + this->protocol = proto[nProtocol-1]; +#else + memcpy_P(&this->protocol, &proto[nProtocol-1], sizeof(Protocol)); +#endif +} + +/** + * Sets the protocol to send with pulse length in microseconds. + */ +void RCSwitch::setProtocol(int nProtocol, int nPulseLength) { + setProtocol(nProtocol); + this->setPulseLength(nPulseLength); +} + + +/** + * Sets pulse length in microseconds + */ +void RCSwitch::setPulseLength(int nPulseLength) { + this->protocol.pulseLength = nPulseLength; +} + +/** + * Sets Repeat Transmits + */ +void RCSwitch::setRepeatTransmit(int nRepeatTransmit) { + this->nRepeatTransmit = nRepeatTransmit; +} + +/** + * Set Receiving Tolerance + */ +#if not defined( RCSwitchDisableReceiving ) +void RCSwitch::setReceiveTolerance(int nPercent) { + RCSwitch::nReceiveTolerance = nPercent; +} +#endif + + +/** + * Enable transmissions + * + * @param nTransmitterPin Arduino Pin to which the sender is connected to + */ +void RCSwitch::enableTransmit(int nTransmitterPin) { + this->nTransmitterPin = nTransmitterPin; + pinMode(this->nTransmitterPin, OUTPUT); +} + +/** + * Disable transmissions + */ +void RCSwitch::disableTransmit() { + this->nTransmitterPin = -1; +} + +/** + * Switch a remote switch on (Type D REV) + * + * @param sGroup Code of the switch group (A,B,C,D) + * @param nDevice Number of the switch itself (1..3) + */ +void RCSwitch::switchOn(char sGroup, int nDevice) { + this->sendTriState( this->getCodeWordD(sGroup, nDevice, true) ); +} + +/** + * Switch a remote switch off (Type D REV) + * + * @param sGroup Code of the switch group (A,B,C,D) + * @param nDevice Number of the switch itself (1..3) + */ +void RCSwitch::switchOff(char sGroup, int nDevice) { + this->sendTriState( this->getCodeWordD(sGroup, nDevice, false) ); +} + +/** + * Switch a remote switch on (Type C Intertechno) + * + * @param sFamily Familycode (a..f) + * @param nGroup Number of group (1..4) + * @param nDevice Number of device (1..4) + */ +void RCSwitch::switchOn(char sFamily, int nGroup, int nDevice) { + this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, true) ); +} + +/** + * Switch a remote switch off (Type C Intertechno) + * + * @param sFamily Familycode (a..f) + * @param nGroup Number of group (1..4) + * @param nDevice Number of device (1..4) + */ +void RCSwitch::switchOff(char sFamily, int nGroup, int nDevice) { + this->sendTriState( this->getCodeWordC(sFamily, nGroup, nDevice, false) ); +} + +/** + * Switch a remote switch on (Type B with two rotary/sliding switches) + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + */ +void RCSwitch::switchOn(int nAddressCode, int nChannelCode) { + this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, true) ); +} + +/** + * Switch a remote switch off (Type B with two rotary/sliding switches) + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + */ +void RCSwitch::switchOff(int nAddressCode, int nChannelCode) { + this->sendTriState( this->getCodeWordB(nAddressCode, nChannelCode, false) ); +} + +/** + * Deprecated, use switchOn(const char* sGroup, const char* sDevice) instead! + * Switch a remote switch on (Type A with 10 pole DIP switches) + * + * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") + * @param nChannelCode Number of the switch itself (1..5) + */ +void RCSwitch::switchOn(const char* sGroup, int nChannel) { + const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; + this->switchOn(sGroup, code[nChannel]); +} + +/** + * Deprecated, use switchOff(const char* sGroup, const char* sDevice) instead! + * Switch a remote switch off (Type A with 10 pole DIP switches) + * + * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") + * @param nChannelCode Number of the switch itself (1..5) + */ +void RCSwitch::switchOff(const char* sGroup, int nChannel) { + const char* code[6] = { "00000", "10000", "01000", "00100", "00010", "00001" }; + this->switchOff(sGroup, code[nChannel]); +} + +/** + * Switch a remote switch on (Type A with 10 pole DIP switches) + * + * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") + * @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111") + */ +void RCSwitch::switchOn(const char* sGroup, const char* sDevice) { + this->sendTriState( this->getCodeWordA(sGroup, sDevice, true) ); +} + +/** + * Switch a remote switch off (Type A with 10 pole DIP switches) + * + * @param sGroup Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111") + * @param sDevice Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111") + */ +void RCSwitch::switchOff(const char* sGroup, const char* sDevice) { + this->sendTriState( this->getCodeWordA(sGroup, sDevice, false) ); +} + + +/** + * Returns a char[13], representing the code word to be send. + * + */ +char* RCSwitch::getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + for (int i = 0; i < 5; i++) { + sReturn[nReturnPos++] = (sGroup[i] == '0') ? 'F' : '0'; + } + + for (int i = 0; i < 5; i++) { + sReturn[nReturnPos++] = (sDevice[i] == '0') ? 'F' : '0'; + } + + sReturn[nReturnPos++] = bStatus ? '0' : 'F'; + sReturn[nReturnPos++] = bStatus ? 'F' : '0'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * Encoding for type B switches with two rotary/sliding switches. + * + * The code word is a tristate word and with following bit pattern: + * + * +-----------------------------+-----------------------------+----------+------------+ + * | 4 bits address | 4 bits address | 3 bits | 1 bit | + * | switch group | switch number | not used | on / off | + * | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | 1=0FFF 2=F0FF 3=FF0F 4=FFF0 | FFF | on=F off=0 | + * +-----------------------------+-----------------------------+----------+------------+ + * + * @param nAddressCode Number of the switch group (1..4) + * @param nChannelCode Number of the switch itself (1..4) + * @param bStatus Whether to switch on (true) or off (false) + * + * @return char[13], representing a tristate code word of length 12 + */ +char* RCSwitch::getCodeWordB(int nAddressCode, int nChannelCode, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + if (nAddressCode < 1 || nAddressCode > 4 || nChannelCode < 1 || nChannelCode > 4) { + return 0; + } + + for (int i = 1; i <= 4; i++) { + sReturn[nReturnPos++] = (nAddressCode == i) ? '0' : 'F'; + } + + for (int i = 1; i <= 4; i++) { + sReturn[nReturnPos++] = (nChannelCode == i) ? '0' : 'F'; + } + + sReturn[nReturnPos++] = 'F'; + sReturn[nReturnPos++] = 'F'; + sReturn[nReturnPos++] = 'F'; + + sReturn[nReturnPos++] = bStatus ? 'F' : '0'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * Like getCodeWord (Type C = Intertechno) + */ +char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + int nFamily = (int)sFamily - 'a'; + if ( nFamily < 0 || nFamily > 15 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) { + return 0; + } + + // encode the family into four bits + sReturn[nReturnPos++] = (nFamily & 1) ? 'F' : '0'; + sReturn[nReturnPos++] = (nFamily & 2) ? 'F' : '0'; + sReturn[nReturnPos++] = (nFamily & 4) ? 'F' : '0'; + sReturn[nReturnPos++] = (nFamily & 8) ? 'F' : '0'; + + // encode the device and group + sReturn[nReturnPos++] = ((nDevice-1) & 1) ? 'F' : '0'; + sReturn[nReturnPos++] = ((nDevice-1) & 2) ? 'F' : '0'; + sReturn[nReturnPos++] = ((nGroup-1) & 1) ? 'F' : '0'; + sReturn[nReturnPos++] = ((nGroup-1) & 2) ? 'F' : '0'; + + // encode the status code + sReturn[nReturnPos++] = '0'; + sReturn[nReturnPos++] = 'F'; + sReturn[nReturnPos++] = 'F'; + sReturn[nReturnPos++] = bStatus ? 'F' : '0'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * Encoding for the REV Switch Type + * + * The code word is a tristate word and with following bit pattern: + * + * +-----------------------------+-------------------+----------+--------------+ + * | 4 bits address | 3 bits address | 3 bits | 2 bits | + * | switch group | device number | not used | on / off | + * | A=1FFF B=F1FF C=FF1F D=FFF1 | 1=0FF 2=F0F 3=FF0 | 000 | on=10 off=01 | + * +-----------------------------+-------------------+----------+--------------+ + * + * Source: http://www.the-intruder.net/funksteckdosen-von-rev-uber-arduino-ansteuern/ + * + * @param sGroup Name of the switch group (A..D, resp. a..d) + * @param nDevice Number of the switch itself (1..3) + * @param bStatus Whether to switch on (true) or off (false) + * + * @return char[13], representing a tristate code word of length 12 + */ +char* RCSwitch::getCodeWordD(char sGroup, int nDevice, bool bStatus) { + static char sReturn[13]; + int nReturnPos = 0; + + // sGroup must be one of the letters in "abcdABCD" + int nGroup = (sGroup >= 'a') ? (int)sGroup - 'a' : (int)sGroup - 'A'; + if ( nGroup < 0 || nGroup > 3 || nDevice < 1 || nDevice > 3) { + return 0; + } + + for (int i = 0; i < 4; i++) { + sReturn[nReturnPos++] = (nGroup == i) ? '1' : 'F'; + } + + for (int i = 1; i <= 3; i++) { + sReturn[nReturnPos++] = (nDevice == i) ? '1' : 'F'; + } + + sReturn[nReturnPos++] = '0'; + sReturn[nReturnPos++] = '0'; + sReturn[nReturnPos++] = '0'; + + sReturn[nReturnPos++] = bStatus ? '1' : '0'; + sReturn[nReturnPos++] = bStatus ? '0' : '1'; + + sReturn[nReturnPos] = '\0'; + return sReturn; +} + +/** + * @param sCodeWord a tristate code word consisting of the letter 0, 1, F + */ +void RCSwitch::sendTriState(const char* sCodeWord) { + // turn the tristate code word into the corresponding bit pattern, then send it + unsigned long code = 0; + unsigned int length = 0; + for (const char* p = sCodeWord; *p; p++) { + code <<= 2L; + switch (*p) { + case '0': + // bit pattern 00 + break; + case 'F': + // bit pattern 01 + code |= 1L; + break; + case '1': + // bit pattern 11 + code |= 3L; + break; + } + length += 2; + } + this->send(code, length); +} + +/** + * @param sCodeWord a binary code word consisting of the letter 0, 1 + */ +void RCSwitch::send(const char* sCodeWord) { + // turn the tristate code word into the corresponding bit pattern, then send it + unsigned long code = 0; + unsigned int length = 0; + for (const char* p = sCodeWord; *p; p++) { + code <<= 1L; + if (*p != '0') + code |= 1L; + length++; + } + this->send(code, length); +} + +/** + * Transmit the first 'length' bits of the integer 'code'. The + * bits are sent from MSB to LSB, i.e., first the bit at position length-1, + * then the bit at position length-2, and so on, till finally the bit at position 0. + */ +void RCSwitch::send(unsigned long code, unsigned int length) { + if (this->nTransmitterPin == -1) + return; + +#if not defined( RCSwitchDisableReceiving ) + // make sure the receiver is disabled while we transmit + int nReceiverInterrupt_backup = nReceiverInterrupt; + if (nReceiverInterrupt_backup != -1) { + this->disableReceive(); + } +#endif + + for (int nRepeat = 0; nRepeat < nRepeatTransmit; nRepeat++) { + for (int i = length-1; i >= 0; i--) { + if (code & (1L << i)) + this->transmit(protocol.one); + else + this->transmit(protocol.zero); + } + this->transmit(protocol.syncFactor); + } + +#if not defined( RCSwitchDisableReceiving ) + // enable receiver again if we just disabled it + if (nReceiverInterrupt_backup != -1) { + this->enableReceive(nReceiverInterrupt_backup); + } +#endif +} + +/** + * Transmit a single high-low pulse. + */ +void RCSwitch::transmit(HighLow pulses) { + uint8_t firstLogicLevel = (this->protocol.invertedSignal) ? LOW : HIGH; + uint8_t secondLogicLevel = (this->protocol.invertedSignal) ? HIGH : LOW; + + digitalWrite(this->nTransmitterPin, firstLogicLevel); + delayMicroseconds( this->protocol.pulseLength * pulses.high); + digitalWrite(this->nTransmitterPin, secondLogicLevel); + delayMicroseconds( this->protocol.pulseLength * pulses.low); +} + + +#if not defined( RCSwitchDisableReceiving ) +/** + * Enable receiving data + */ +void RCSwitch::enableReceive(int interrupt) { + this->nReceiverInterrupt = interrupt; + this->enableReceive(); +} + +void RCSwitch::enableReceive() { + if (this->nReceiverInterrupt != -1) { + RCSwitch::nReceivedValue = 0; + RCSwitch::nReceivedBitlength = 0; +#if defined(RaspberryPi) // Raspberry Pi + wiringPiISR(this->nReceiverInterrupt, INT_EDGE_BOTH, &handleInterrupt); +#else // Arduino + attachInterrupt(this->nReceiverInterrupt, handleInterrupt, CHANGE); +#endif + } +} + +/** + * Disable receiving data + */ +void RCSwitch::disableReceive() { +#if not defined(RaspberryPi) // Arduino + detachInterrupt(this->nReceiverInterrupt); +#endif // For Raspberry Pi (wiringPi) you can't unregister the ISR + this->nReceiverInterrupt = -1; +} + +bool RCSwitch::available() { + return RCSwitch::nReceivedValue != 0; +} + +void RCSwitch::resetAvailable() { + RCSwitch::nReceivedValue = 0; +} + +unsigned long RCSwitch::getReceivedValue() { + return RCSwitch::nReceivedValue; +} + +unsigned int RCSwitch::getReceivedBitlength() { + return RCSwitch::nReceivedBitlength; +} + +unsigned int RCSwitch::getReceivedDelay() { + return RCSwitch::nReceivedDelay; +} + +unsigned int RCSwitch::getReceivedProtocol() { + return RCSwitch::nReceivedProtocol; +} + +unsigned int* RCSwitch::getReceivedRawdata() { + return RCSwitch::timings; +} + +/* helper function for the receiveProtocol method */ +static inline unsigned int diff(int A, int B) { + return abs(A - B); +} + +/** + * + */ +bool RECEIVE_ATTR RCSwitch::receiveProtocol(const int p, unsigned int changeCount) { +#ifdef ESP8266 + const Protocol &pro = proto[p-1]; +#else + Protocol pro; + memcpy_P(&pro, &proto[p-1], sizeof(Protocol)); +#endif + + unsigned long code = 0; + //Assuming the longer pulse length is the pulse captured in timings[0] + const unsigned int syncLengthInPulses = ((pro.syncFactor.low) > (pro.syncFactor.high)) ? (pro.syncFactor.low) : (pro.syncFactor.high); + const unsigned int delay = RCSwitch::timings[0] / syncLengthInPulses; + const unsigned int delayTolerance = delay * RCSwitch::nReceiveTolerance / 100; + + /* For protocols that start low, the sync period looks like + * _________ + * _____________| |XXXXXXXXXXXX| + * + * |--1st dur--|-2nd dur-|-Start data-| + * + * The 3rd saved duration starts the data. + * + * For protocols that start high, the sync period looks like + * + * ______________ + * | |____________|XXXXXXXXXXXXX| + * + * |-filtered out-|--1st dur--|--Start data--| + * + * The 2nd saved duration starts the data + */ + const unsigned int firstDataTiming = (pro.invertedSignal) ? (2) : (1); + + for (unsigned int i = firstDataTiming; i < changeCount - 1; i += 2) { + code <<= 1; + if (diff(RCSwitch::timings[i], delay * pro.zero.high) < delayTolerance && + diff(RCSwitch::timings[i + 1], delay * pro.zero.low) < delayTolerance) { + // zero + } else if (diff(RCSwitch::timings[i], delay * pro.one.high) < delayTolerance && + diff(RCSwitch::timings[i + 1], delay * pro.one.low) < delayTolerance) { + // one + code |= 1; + } else { + // Failed + return false; + } + } + + if (changeCount > 7) { // ignore very short transmissions: no device sends them, so this must be noise + RCSwitch::nReceivedValue = code; + RCSwitch::nReceivedBitlength = (changeCount - 1) / 2; + RCSwitch::nReceivedDelay = delay; + RCSwitch::nReceivedProtocol = p; + return true; + } + + return false; +} + +void RECEIVE_ATTR RCSwitch::handleInterrupt() { + + static unsigned int changeCount = 0; + static unsigned long lastTime = 0; + static unsigned int repeatCount = 0; + + const long time = micros(); + const unsigned int duration = time - lastTime; + + if (duration > RCSwitch::nSeparationLimit) { + // A long stretch without signal level change occurred. This could + // be the gap between two transmission. + if (diff(duration, RCSwitch::timings[0]) < 200) { + // This long signal is close in length to the long signal which + // started the previously recorded timings; this suggests that + // it may indeed by a a gap between two transmissions (we assume + // here that a sender will send the signal multiple times, + // with roughly the same gap between them). + repeatCount++; + if (repeatCount == 2) { + for(unsigned int i = 1; i <= numProto; i++) { + if (receiveProtocol(i, changeCount)) { + // receive succeeded for protocol i + break; + } + } + repeatCount = 0; + } + } + changeCount = 0; + } + + // detect overflow + if (changeCount >= RCSWITCH_MAX_CHANGES) { + changeCount = 0; + repeatCount = 0; + } + + RCSwitch::timings[changeCount++] = duration; + lastTime = time; +} +#endif diff --git a/chicken_stall_door_software/RCSwitch.h b/chicken_stall_door_software/RCSwitch.h new file mode 100644 index 0000000..b7755e0 --- /dev/null +++ b/chicken_stall_door_software/RCSwitch.h @@ -0,0 +1,184 @@ +/* + RCSwitch - Arduino libary for remote control outlet switches + Copyright (c) 2011 Suat Özgür. All right reserved. + + Contributors: + - Andre Koehler / info(at)tomate-online(dot)de + - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com + - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46 + - Dominik Fischer / dom_fischer(at)web(dot)de + - Frank Oltmanns / .(at)gmail(dot)com + - Max Horn / max(at)quendi(dot)de + - Robert ter Vehn / .(at)gmail(dot)com + + Project home: https://github.com/sui77/rc-switch/ + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _RCSwitch_h +#define _RCSwitch_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#elif defined(ENERGIA) // LaunchPad, FraunchPad and StellarPad specific + #include "Energia.h" +#elif defined(RPI) // Raspberry Pi + #define RaspberryPi + + // Include libraries for RPi: + #include /* memcpy */ + #include /* abs */ + #include +#elif defined(SPARK) + #include "application.h" +#else + #include "WProgram.h" +#endif + +#include + + +// At least for the ATTiny X4/X5, receiving has to be disabled due to +// missing libm depencies (udivmodhi4) +#if defined( __AVR_ATtinyX5__ ) or defined ( __AVR_ATtinyX4__ ) +#define RCSwitchDisableReceiving +#endif + +// Number of maximum high/Low changes per packet. +// We can handle up to (unsigned long) => 32 bit * 2 H/L changes per bit + 2 for sync +#define RCSWITCH_MAX_CHANGES 67 + +class RCSwitch { + + public: + RCSwitch(); + + void switchOn(int nGroupNumber, int nSwitchNumber); + void switchOff(int nGroupNumber, int nSwitchNumber); + void switchOn(const char* sGroup, int nSwitchNumber); + void switchOff(const char* sGroup, int nSwitchNumber); + void switchOn(char sFamily, int nGroup, int nDevice); + void switchOff(char sFamily, int nGroup, int nDevice); + void switchOn(const char* sGroup, const char* sDevice); + void switchOff(const char* sGroup, const char* sDevice); + void switchOn(char sGroup, int nDevice); + void switchOff(char sGroup, int nDevice); + + void sendTriState(const char* sCodeWord); + void send(unsigned long code, unsigned int length); + void send(const char* sCodeWord); + + #if not defined( RCSwitchDisableReceiving ) + void enableReceive(int interrupt); + void enableReceive(); + void disableReceive(); + bool available(); + void resetAvailable(); + + unsigned long getReceivedValue(); + unsigned int getReceivedBitlength(); + unsigned int getReceivedDelay(); + unsigned int getReceivedProtocol(); + unsigned int* getReceivedRawdata(); + #endif + + void enableTransmit(int nTransmitterPin); + void disableTransmit(); + void setPulseLength(int nPulseLength); + void setRepeatTransmit(int nRepeatTransmit); + #if not defined( RCSwitchDisableReceiving ) + void setReceiveTolerance(int nPercent); + #endif + + /** + * Description of a single pule, which consists of a high signal + * whose duration is "high" times the base pulse length, followed + * by a low signal lasting "low" times the base pulse length. + * Thus, the pulse overall lasts (high+low)*pulseLength + */ + struct HighLow { + uint8_t high; + uint8_t low; + }; + + /** + * A "protocol" describes how zero and one bits are encoded into high/low + * pulses. + */ + struct Protocol { + /** base pulse length in microseconds, e.g. 350 */ + uint16_t pulseLength; + + HighLow syncFactor; + HighLow zero; + HighLow one; + + /** + * If true, interchange high and low logic levels in all transmissions. + * + * By default, RCSwitch assumes that any signals it sends or receives + * can be broken down into pulses which start with a high signal level, + * followed by a a low signal level. This is e.g. the case for the + * popular PT 2260 encoder chip, and thus many switches out there. + * + * But some devices do it the other way around, and start with a low + * signal level, followed by a high signal level, e.g. the HT6P20B. To + * accommodate this, one can set invertedSignal to true, which causes + * RCSwitch to change how it interprets any HighLow struct FOO: It will + * then assume transmissions start with a low signal lasting + * FOO.high*pulseLength microseconds, followed by a high signal lasting + * FOO.low*pulseLength microseconds. + */ + bool invertedSignal; + }; + + void setProtocol(Protocol protocol); + void setProtocol(int nProtocol); + void setProtocol(int nProtocol, int nPulseLength); + + private: + char* getCodeWordA(const char* sGroup, const char* sDevice, bool bStatus); + char* getCodeWordB(int nGroupNumber, int nSwitchNumber, bool bStatus); + char* getCodeWordC(char sFamily, int nGroup, int nDevice, bool bStatus); + char* getCodeWordD(char group, int nDevice, bool bStatus); + void transmit(HighLow pulses); + + #if not defined( RCSwitchDisableReceiving ) + static void handleInterrupt(); + static bool receiveProtocol(const int p, unsigned int changeCount); + int nReceiverInterrupt; + #endif + int nTransmitterPin; + int nRepeatTransmit; + + Protocol protocol; + + #if not defined( RCSwitchDisableReceiving ) + static int nReceiveTolerance; + volatile static unsigned long nReceivedValue; + volatile static unsigned int nReceivedBitlength; + volatile static unsigned int nReceivedDelay; + volatile static unsigned int nReceivedProtocol; + const static unsigned int nSeparationLimit; + /* + * timings[0] contains sync timing, followed by a number of bits + */ + static unsigned int timings[RCSWITCH_MAX_CHANGES]; + #endif + + +}; + +#endif diff --git a/chicken_stall_door_software/chicken_stall_door_software.ino b/chicken_stall_door_software/chicken_stall_door_software.ino new file mode 100644 index 0000000..a536858 --- /dev/null +++ b/chicken_stall_door_software/chicken_stall_door_software.ino @@ -0,0 +1,300 @@ +/* + Hasenklappe + Author: dustinbrun + licensed under CC BY 4.0 + Version 09.2022 + + Source 433Mhz Receiver: https://funduino.de/nr-03-433mhz-funkverbindung + + + ------ Serial Communication, Baud: 9600 ------ + - Request State of the Door: 'S' + - Returns: 1 - Door is open, 0 - Door is closed, 2 - Unknown Position + - Open Door: 'U' + - Returns: 'U' + - If it fails or not possible (already open): 'E' + - Open Door: 'D' + - Returns: 'D' + - If it fails or not possible (already closed): 'E' + - Stop Movement: 'H' + - Returns: 'H' + +*/ + + +#include "RCSwitch.h" // https://github.com/sui77/rc-switch + +//Variables +int motor_timeout = 20000; //Motor stops after this limit (in ms) is reached, Maximum time for the Opening-/Closing- Process +int solenoid_on_time = 2000; //Time for the solenoid to be switched on (in ms) after the opening process was started. The door should exceed the solenoid position within this interval + +//Remote Codes +long int remote_code_up = 5592512; //Received Remote Codes, to which the Arduino reacts to, change to the Values of your remote. You can read those code using the "ReceiveDemo_Simple.ino"-code from the rc-switch Library +long int remote_code_down = 5592368; +long int remote_code_stop = 5592332; + + +// Pins +const int button_down = 12; +const int button_up = 11; +const int button_stop = 10; +const int led_error = 9; +const int led_motor = 8; +const int led_power = 7; +const int motor_a = 6; +const int motor_b = 5; +const int limit_top = A1; +const int limit_bottom = A2; +const int remote_interrupt_pin = 0; // Interrupt-Pin 0 = Arduino Pin D2 +const int lock_pin = 4; + + +unsigned long motor_start_time = 0; +bool go_up = false; +bool go_down = false; +bool stop = false; + +RCSwitch remote = RCSwitch(); + +void setup() +{ + Serial.begin(9600); + remote.enableReceive(remote_interrupt_pin); + + pinMode(button_down, INPUT_PULLUP); //Pressed = LOW + pinMode(button_up, INPUT_PULLUP); + pinMode(button_stop, INPUT_PULLUP); + pinMode(led_error, OUTPUT); + pinMode(led_motor, OUTPUT); + pinMode(led_power, OUTPUT); + pinMode(motor_a, OUTPUT); + pinMode(motor_b, OUTPUT); + pinMode(lock_pin, OUTPUT); + pinMode(limit_top, INPUT_PULLUP); //Triggered = LOW + pinMode(limit_bottom, INPUT_PULLUP); + + digitalWrite(motor_a, LOW); + digitalWrite(motor_b, LOW); + digitalWrite(lock_pin, LOW); + digitalWrite(led_error, HIGH); + digitalWrite(led_motor, HIGH); + digitalWrite(led_power, HIGH); + + delay(200); + digitalWrite(led_error, LOW); + digitalWrite(led_motor, LOW); + + //Serial.print("A"); +} + +void loop() +{ + if ((digitalRead(button_down) == LOW || go_down) && digitalRead(limit_bottom) != LOW) + { + delay(100); + stop = false; + motor_start_time = millis(); + motor_down(); + + while (digitalRead(limit_bottom) != LOW && + millis() - motor_start_time < motor_timeout && + stop != true) + { + check_messages(); + delay(10); + } + + motor_stop(); + + if ((millis() - motor_start_time >= motor_timeout) || stop == true) + { + digitalWrite(led_error, HIGH); + Serial.print("E"); + delay(500); + digitalWrite(led_error, LOW); + } + else + { + digitalWrite(led_error, LOW); + Serial.print(get_pos()); + } + + go_down = false; + stop = false; + delay(200); + } + else if ((digitalRead(button_down) == LOW || go_down) && digitalRead(limit_bottom) == LOW) + { + digitalWrite(led_error, HIGH); + Serial.print("E"); + delay(500); + digitalWrite(led_error, LOW); + go_down = false; + + } + + + if ((digitalRead(button_up) == LOW || go_up) && digitalRead(limit_top) != LOW) + { + delay(100); + stop = false; + door_unlock(); + motor_start_time = millis(); + motor_up(); + + while (digitalRead(limit_top) != LOW && + millis() - motor_start_time < motor_timeout && + stop != true) + { + check_messages(); + delay(10); + if(millis() - motor_start_time > solenoid_on_time) + { + door_lock(); + } + } + + motor_stop(); + door_lock(); + + if ((millis() - motor_start_time >= motor_timeout) || stop == true) + { + digitalWrite(led_error, HIGH); + Serial.print("E"); + delay(500); + digitalWrite(led_error, LOW); + } + else + { + digitalWrite(led_error, LOW); + Serial.print(get_pos()); + } + + go_up = false; + stop = false; + delay(200); + } + else if ((digitalRead(button_up) == LOW || go_up) && digitalRead(limit_top) == LOW) + { + digitalWrite(led_error, HIGH); + Serial.print("E"); + delay(500); + digitalWrite(led_error, LOW); + go_up = false; + + } + + check_messages(); + delay(10); + +} + +void check_messages() +{ + if (Serial.available()) { + char serial_message = Serial.read(); + led_power_blink(); + + if (serial_message == 'S') // Get State + { + Serial.print(get_pos()); // 1 - Door is open, 0 - Door is closed, 2 - Unknown Position + } + else if (serial_message == 'U') + { + go_up = true; + Serial.print("U"); + } + else if (serial_message == 'D') + { + go_down = true; + Serial.print("D"); + } + else if (serial_message == 'H') + { + stop = true; + Serial.print("H"); + } + } + + if (remote.available()) + { + long int remote_message = remote.getReceivedValue(); + led_power_blink(); + + if (remote_message == remote_code_up) + { + go_up = true; + } + else if (remote_message == remote_code_down) + { + go_down = true; + } + else if (remote_message == remote_code_stop) + { + stop = true; + } + /*else // Unknown Code + { + digitalWrite(led_error, HIGH); + Serial.print("E"); + delay(100); + digitalWrite(led_error, LOW); + }*/ + + remote.resetAvailable(); + } + + if (digitalRead(button_stop) == LOW) + { + stop = true; + } + +} + + +int get_pos() +{ + if (digitalRead(limit_top) == LOW) return 1; + else if (digitalRead(limit_bottom) == LOW) return 0; + else return 2; +} + + +void motor_down() +{ + digitalWrite(led_motor, HIGH); + digitalWrite(motor_a, LOW); //Motor going down + digitalWrite(motor_b, HIGH); +} +void motor_up() +{ + digitalWrite(led_motor, HIGH); + digitalWrite(motor_a, HIGH); //Motor going up + digitalWrite(motor_b, LOW); +} +void motor_stop() +{ + digitalWrite(led_motor, LOW); + digitalWrite(motor_a, LOW); //Motor stop + digitalWrite(motor_b, LOW); +} + +void door_lock() //Normally closed Solenoid Lock +{ + digitalWrite(lock_pin, LOW); +} +void door_unlock() +{ + digitalWrite(led_motor, HIGH); + digitalWrite(lock_pin, HIGH); + delay(200); + digitalWrite(led_motor, LOW); + delay(200); +} + +void led_power_blink() +{ + digitalWrite(led_power, LOW); + delay(50); + digitalWrite(led_power, HIGH); +} diff --git a/chicken_stall_door_software/rc-switch-master.zip b/chicken_stall_door_software/rc-switch-master.zip new file mode 100644 index 0000000..9edb763 Binary files /dev/null and b/chicken_stall_door_software/rc-switch-master.zip differ diff --git a/pictures/433_MHz_receiver.jpg b/pictures/433_MHz_receiver.jpg new file mode 100644 index 0000000..2174e82 Binary files /dev/null and b/pictures/433_MHz_receiver.jpg differ diff --git a/pictures/Motor_Driver_Board_TLE4202B_Leiterplatte.png b/pictures/Motor_Driver_Board_TLE4202B_Leiterplatte.png new file mode 100644 index 0000000..d294335 Binary files /dev/null and b/pictures/Motor_Driver_Board_TLE4202B_Leiterplatte.png differ diff --git a/pictures/Motor_Driver_Board_TLE4202B_Schaltplan.png b/pictures/Motor_Driver_Board_TLE4202B_Schaltplan.png new file mode 100644 index 0000000..561c76e Binary files /dev/null and b/pictures/Motor_Driver_Board_TLE4202B_Schaltplan.png differ diff --git a/pictures/Motor_Winch_Drum_3D_Model.PNG b/pictures/Motor_Winch_Drum_3D_Model.PNG new file mode 100644 index 0000000..6a7e90c Binary files /dev/null and b/pictures/Motor_Winch_Drum_3D_Model.PNG differ diff --git a/pictures/Solenoid_Lock_1.jpg b/pictures/Solenoid_Lock_1.jpg new file mode 100644 index 0000000..5e31cac Binary files /dev/null and b/pictures/Solenoid_Lock_1.jpg differ diff --git a/pictures/Solenoid_Lock_2.jpg b/pictures/Solenoid_Lock_2.jpg new file mode 100644 index 0000000..29057ad Binary files /dev/null and b/pictures/Solenoid_Lock_2.jpg differ diff --git a/pictures/Solenoid_Lock_Mount_3D_Model.PNG b/pictures/Solenoid_Lock_Mount_3D_Model.PNG new file mode 100644 index 0000000..8b66cea Binary files /dev/null and b/pictures/Solenoid_Lock_Mount_3D_Model.PNG differ diff --git a/pictures/chicken_stall_door.jpg b/pictures/chicken_stall_door.jpg new file mode 100644 index 0000000..19ed5d8 Binary files /dev/null and b/pictures/chicken_stall_door.jpg differ diff --git a/pictures/chicken_stall_door_frontpanel.pdf b/pictures/chicken_stall_door_frontpanel.pdf new file mode 100644 index 0000000..5ed503c Binary files /dev/null and b/pictures/chicken_stall_door_frontpanel.pdf differ diff --git a/pictures/chicken_stall_door_frontpanel_125x70mm.png b/pictures/chicken_stall_door_frontpanel_125x70mm.png new file mode 100644 index 0000000..0b8b408 Binary files /dev/null and b/pictures/chicken_stall_door_frontpanel_125x70mm.png differ diff --git a/pictures/chicken_stall_door_schematic_Leiterplatte.png b/pictures/chicken_stall_door_schematic_Leiterplatte.png new file mode 100644 index 0000000..7e14601 Binary files /dev/null and b/pictures/chicken_stall_door_schematic_Leiterplatte.png differ diff --git a/pictures/chicken_stall_door_schematic_Schaltplan.png b/pictures/chicken_stall_door_schematic_Schaltplan.png new file mode 100644 index 0000000..3449516 Binary files /dev/null and b/pictures/chicken_stall_door_schematic_Schaltplan.png differ diff --git a/pictures/chicken_stall_door_text.jpg b/pictures/chicken_stall_door_text.jpg new file mode 100644 index 0000000..cb8cdf7 Binary files /dev/null and b/pictures/chicken_stall_door_text.jpg differ