Blindcontroller with PB local control
-
Hello,
This sketch can be used for control a blind (local + remote) it is working but there is still room for improvement ... to say the least
Any suggestions remarks are welcome
heu it isnottested in arduino dev (now it is 2016/08/03) (i use eclipse arduino IDE)Update 2016/08/03:
insert files as requested by TheoL (and removed the ZIP file
**ATTENTION ** i could not make it in one post so you need also part 2 !!This is the main sketch (and corrected smal bug)
"Rfm69_BlindController.ino"
// Do not remove the include below #include "Rfm69_BlindController.h" #include "Bl_Control.h" /* BlindController for MySensors (or any other local/remote control ) This is a adaption off this sketch (garage door control) original is @ https://forum.mysensors.org/topic/4059/automated-garage-door/21 from user https://forum.mysensors.org/user/dbemowsk However not much is still in it July 20,2016 This will allow the MySensors gateway to monitor and control your blinds. This sketch monitors the actual position of the blind(s). It CAN use a sens input to detect current flowing (true/false) when the blind is running and current stops (current gets zero) we know the demanded position limit is reached, in the required direction (hard limit up/down). This sketch features the following: Allows you to monitor and control the blind position and return it's status based on the following: 1 - use two local inputs (buttons) to increase or decrease the position 2 - sending a setpoint/position request by a host (Mysensor here) 3 - Send a Factor (gain) to fine tune the time run up/down) PARTS LIST:(for 2 blinds) Moteino 4 3 or 5v relay 4 2N3904 transistor for relay 4 1k resistor 2 2k7 Resistor (for Led) 2 Led (error /run) HW CONFIG; (here moteino) Relay UP D5 (Blind1) Relay Down D6 (Blind1) Error/Run Led D7 (Blind1) InputUP D14 (Blind1) InputDown D15 (Blind1) Currentsense D16 (Blind1) Relay UP D3 (Blind2) Relay Down D4 (Blind2) Error/Run Led D17 (Blind2) InputUP D18 (Blind2) InputDown D19 (Blind2) Currentsense A6 (Blind2) Version history: 0.1 : Refractor original source (garage opener see ref) 0.2 : Build original concept without object 0.3 : Rebuil d with object class 0.4 : Added a factor (gain) to allow a wider use and removed "magic numbers" in code 0.5 : Added code for retrieving factor from EEPROM : added fix for nasty RT-bug when gain gets higher then 131 (integer overflow) */ #include <MyTransportRFM69.h> #include <MyHwATMega328.h> #include <MySensor.h> //#include <SPI.h> #define SKETCH_NAME "BlindController" #define SKETCH_VERSION "0.4" #define NODE_ID 85 //Define The node <> 0 (i don't want a auto assignment see setup @mySensors) #define CHILD_ID_SP_BL1 52 //Here we receive the setpoint #define CHILD_ID_GAIN_BL1 53 //This is the gain (value from 10 to 1000%!) #define CHILD_ID_SP_BL2 55 //Here we receive the setpoint #define CHILD_ID_GAIN_BL2 56 //This is the gain (value from 10 to 1000%!) #define EPROM_GAIN_BL1 1 //This is the location where the factor is stored (Blind1) //After a power failure this value will be restored #define EPROM_GAIN_BL2 3 //This is the location where the factor is stored (Blind2) #define DI_BL1_UP 14 //Pin used for command up #define DI_BL1_DWN 15 //Pin used for command Down #define AI_BL1_SENS 16 //Pin used Sensing the current (Yes/no) #define DO_BL1_MOTOR_UP 5 //Pin activate relay up #define DO_BL1_MOTOR_DWN 6 //Pin activate Relay Down #define DO_BL1_ERR_RUN 7 //Pin activate Run/error #define DI_BL2_UP 18 //Pin used for command up #define DI_BL2_DWN 19 //Pin used for command Down #define AI_BL2_SENS A6 //Pin used Sensing the current (Yes/no) #define DO_BL2_MOTOR_UP 3 //Pin activate relay up #define DO_BL2_MOTOR_DWN 4 //Pin activate Relay Down #define DO_BL2_ERR_RUN 17 //Pin activate Run/error #define MOTOR_ON 0 // GPIO value to write to turn on attached relay #define MOTOR_OFF 1 // GPIO value to write to turn off attached relay #define DEBUG_BLINDCONTROLLER 0 // Set to non zero for debugging // new V_TEXT variable type (development 20150905) //const int V_TEXT = 47 ; // new S_INFO sensor type (development 20150905) //const int S_INFO = 36 ; //Some timer logic to do updates on the inputs unsigned long bl_previoustMillis_inp_scan =0; MyMessage msg(CHILD_ID_SP_BL1, V_PERCENTAGE);// global response object MyTransportRFM69 transport; //Attention most users will use a NRF here!! // Hardware profile MyHwATMega328 hw; MySensor gw(transport,hw); //MyMessage msgStatus(CHILD_ID_STATUS, V_TEXT); Bl_Control blind1,blind2; //Handle to controller Instance /** * Function Prototypes */ // hmm.. seems not needed in Arduino /** * setup - Initialize the HW */ void setup() { //BLIND1 //initialize the inputs Blind1 pinMode(DI_BL1_UP, INPUT_PULLUP); pinMode(DI_BL1_DWN, INPUT_PULLUP); pinMode(AI_BL1_SENS,INPUT_PULLUP); //Set outputs to safe state and init digitalWrite( DO_BL1_MOTOR_UP,MOTOR_OFF); digitalWrite( DO_BL1_MOTOR_DWN,MOTOR_OFF); digitalWrite(DO_BL1_ERR_RUN ,MOTOR_OFF); pinMode(DO_BL1_MOTOR_UP, OUTPUT); pinMode(DO_BL1_MOTOR_DWN, OUTPUT); pinMode(DO_BL1_ERR_RUN, OUTPUT); //BLIND2 //TODO: Test extra controller pinMode(DI_BL2_UP, INPUT_PULLUP); pinMode(DI_BL2_DWN, INPUT_PULLUP); //pinMode(AI_BL2_SENS,INPUT_PULLUP); //Set outputs to safe state and init digitalWrite( DO_BL2_MOTOR_UP,MOTOR_OFF); digitalWrite( DO_BL2_MOTOR_DWN,MOTOR_OFF); digitalWrite(DO_BL2_ERR_RUN ,MOTOR_OFF); pinMode(DO_BL2_MOTOR_UP, OUTPUT); pinMode(DO_BL2_MOTOR_DWN, OUTPUT); pinMode(DO_BL2_ERR_RUN, OUTPUT); #if NODE_ID gw.begin( incomingMessage, NODE_ID,false); //No repeater NODE_ID is defined #else gw.begin( incomingMessage); //No NODE_ID auto from GW (Host) will give you one #endif gw.sendSketchInfo( SKETCH_NAME, SKETCH_VERSION ); // Register the blind to the gateway gw.present( CHILD_ID_SP_BL1, S_DIMMER,"Position" ); gw.present(CHILD_ID_GAIN_BL1, S_DIMMER, "Gain"); gw.present( CHILD_ID_SP_BL2, S_DIMMER,"Blind 2 Pos" ); gw.present(CHILD_ID_GAIN_BL2, S_DIMMER, "Blind 2 Gain"); //init the scan clock bl_previoustMillis_inp_scan = millis()+1000; //Give a offset from the current moment (1000msec) to complete the setup //Restore from local EEprom the factor/gain int Gain=gw.loadState(EPROM_GAIN_BL1); //Get the gain from the EEPROM Gain = highByte(Gain) ; Gain|= lowByte( gw.loadState(EPROM_GAIN_BL1+1)); //Get the gain from the EEPROM blind1.Set_Gain_PV((uint16_t ) Gain); //Store the gain in controller obj Blind1 msg.setSensor(CHILD_ID_GAIN_BL1); //Set startup gain to host msg.set(Gain); gw.send( msg); //send it to the host Gain=gw.loadState(EPROM_GAIN_BL2); //Get the gain from the EEPROM Gain = highByte(Gain); Gain|= lowByte( gw.loadState(EPROM_GAIN_BL2+1)); //Get the gain from the EEPROM blind2.Set_Gain_PV((uint16_t ) Gain); //Store the gain in controller obj Blind1 msg.setSensor(CHILD_ID_GAIN_BL2); //Set startup gain to host msg.set(Gain); gw.send( msg); //send it to the host // Pull the gateway's current Blind level - restore level upon sender node power-up gw.request( CHILD_ID_SP_BL1, V_DIMMER ); // Pull the gateway's current Blind level - restore level upon sender node power-up gw.request( CHILD_ID_SP_BL2, V_DIMMER ); } void tasks (){ uint8_t tmp_Current , tmp_Current2; uint8_t tmp_up, tmp_up2; uint8_t tmp_dwn , tmp_dwn2; static uint8_t st_PrevOutput_State =0; //Local memory only update when something static uint8_t st_PrevOutput_State2 =0; uint8_t tmp_CurrentOutput_State,tmp_CurrentOutput_State2; //Read local inputs blind1 tmp_up = digitalRead(DI_BL1_UP )==false; tmp_dwn = digitalRead(DI_BL1_DWN)==false; //TODO: Test proof of concept: tmp_Current =1; // digitalRead(t_BL_InBits::CURSNS)==false; //Read local inputs blind1 tmp_up2 = digitalRead(DI_BL2_UP )==false; tmp_dwn2 = digitalRead(DI_BL2_DWN)==false; tmp_Current2 =1; //tmp_Current2= analogRead(AI_BL2_SENS) > 100 ? 0 : 1; blind1.bl_SetInputs(tmp_up,tmp_dwn,tmp_Current); tmp_CurrentOutput_State =blind1.bl_GetOutputs(); blind2.bl_SetInputs(tmp_up2,tmp_dwn2,tmp_Current2); tmp_CurrentOutput_State2 = blind2.bl_GetOutputs(); // Somethings changed do actions if (st_PrevOutput_State != tmp_CurrentOutput_State){ if (bitRead(tmp_CurrentOutput_State, t_BL_OutBits::BL_RN_ERR )) { digitalWrite(DO_BL1_MOTOR_UP,MOTOR_OFF); digitalWrite(DO_BL1_MOTOR_DWN,MOTOR_OFF); digitalWrite(DO_BL1_ERR_RUN,MOTOR_ON); #if DEBUG_BLINDCONTROLLER Serial.println("RN_ERR"); #endif } if (bitRead(tmp_CurrentOutput_State, t_BL_OutBits::BL_RN_UP )) { digitalWrite(DO_BL1_MOTOR_UP,MOTOR_ON); digitalWrite(DO_BL1_MOTOR_DWN,MOTOR_OFF); #if DEBUG_BLINDCONTROLLER Serial.println("RN_U"); #endif }else{ digitalWrite(DO_BL1_MOTOR_UP,MOTOR_OFF); #if DEBUG_BLINDCONTROLLER Serial.println("RN_S1"); #endif } if (bitRead(tmp_CurrentOutput_State, t_BL_OutBits::BL_RN_DWN )) { digitalWrite(DO_BL1_MOTOR_UP,MOTOR_OFF); digitalWrite(DO_BL1_MOTOR_DWN,MOTOR_ON); #if DEBUG_BLINDCONTROLLER Serial.println("RN_D"); #endif }else{ digitalWrite(DO_BL1_MOTOR_DWN,MOTOR_OFF); #if DEBUG_BLINDCONTROLLER Serial.println("RN_S2"); #endif } st_PrevOutput_State = tmp_CurrentOutput_State; if (blind1.Get_PV() >100){ msg.setSensor(CHILD_ID_SP_BL1); msg.setType(V_PERCENTAGE); gw.send( msg.set(100) ); //Inform the host that we are on the max } else{ msg.setSensor(CHILD_ID_SP_BL1); msg.setType(V_PERCENTAGE); gw.send( msg.set(blind1.Get_PV()) ); } } // Somethings changed for blind2 do actions if (st_PrevOutput_State2 != tmp_CurrentOutput_State2){ if (bitRead(tmp_CurrentOutput_State2, t_BL_OutBits::BL_RN_ERR )) { digitalWrite(DO_BL2_MOTOR_UP,MOTOR_OFF); digitalWrite(DO_BL2_MOTOR_DWN,MOTOR_OFF); digitalWrite(DO_BL2_ERR_RUN,MOTOR_ON); #if DEBUG_BLINDCONTROLLER Serial.println("RNERR2"); #endif } if (bitRead(tmp_CurrentOutput_State2, t_BL_OutBits::BL_RN_UP )) { digitalWrite(DO_BL2_MOTOR_UP,MOTOR_ON); digitalWrite(DO_BL2_MOTOR_DWN,MOTOR_OFF); #if DEBUG_BLINDCONTROLLER Serial.println("RNU2"); #endif }else{ digitalWrite(DO_BL2_MOTOR_UP,MOTOR_OFF); #if DEBUG_BLINDCONTROLLER Serial.println("RNS12"); #endif } if (bitRead(tmp_CurrentOutput_State2, t_BL_OutBits::BL_RN_DWN )) { digitalWrite(DO_BL2_MOTOR_UP,MOTOR_OFF); digitalWrite(DO_BL2_MOTOR_DWN,MOTOR_ON); #if DEBUG_BLINDCONTROLLER Serial.println("RN_D2"); #endif }else{ digitalWrite(DO_BL2_MOTOR_DWN,MOTOR_OFF); #if DEBUG_BLINDCONTROLLER Serial.println("RNS2"); #endif } st_PrevOutput_State2 = tmp_CurrentOutput_State2; if (blind2.Get_PV() >100){ msg.setSensor(CHILD_ID_SP_BL2); msg.setType(V_PERCENTAGE); gw.send( msg.set(100) ); //Inform the host that we are on the max } else{ msg.setSensor(CHILD_ID_SP_BL2); msg.setType(V_PERCENTAGE); gw.send( msg.set(blind2.Get_PV()) ); } } //Update the eye candy (feedback) Blind1 if (blind1.bl_RunErrorLed() ){ digitalWrite(DO_BL1_ERR_RUN,MOTOR_ON); }else{ digitalWrite(DO_BL1_ERR_RUN,MOTOR_OFF); } //Update the eye candy (feedback) Blind2 if (blind2.bl_RunErrorLed() ){ digitalWrite(DO_BL2_ERR_RUN,MOTOR_ON); }else{ digitalWrite(DO_BL2_ERR_RUN,MOTOR_OFF); } } /** * loop - The main program loop */ void loop() { // Alway process incoming messages whenever possible gw.process(); blind1.bl_Process(); blind2.bl_Process(); tasks(); } /** * incomingMessage - Process the incoming messages * From the host */ void incomingMessage( const MyMessage &message ) { uint16_t tmp_Faktor; uint8_t New_sp =0; uint8_t New_Cmd=0; if (message.sensor ==CHILD_ID_SP_BL1 ) { if (message.type == V_DIMMER) { // This is a SP change request int val = message.getInt();//retrieve SP from message bitSet(New_Cmd,0); New_sp = 0xff & val; //set SP to the requested value }else if (message.type==V_STATUS) {//Position request (full open / full closed) if (!message.getBool()) { //I Use a inverted blind on the GUI bitSet(New_Cmd,1); //Fire to full close }else{ bitSet(New_Cmd,2); //Fire to full open } } blind1.UpdateRemote_cmd(New_sp,New_Cmd);//Send it to the controller }else if (message.sensor ==CHILD_ID_GAIN_BL1 ){ tmp_Faktor = message.getInt(); if(tmp_Faktor != 0) blind1.Set_Gain_PV(tmp_Faktor); gw.saveState(EPROM_GAIN_BL1,highByte( tmp_Faktor)); gw.saveState(EPROM_GAIN_BL1+1,lowByte( tmp_Faktor)); }else if (message.sensor ==CHILD_ID_SP_BL2 ){ if (message.type == V_DIMMER) { // This is a SP change request int val = message.getInt();//retrieve SP from message bitSet(New_Cmd,0); New_sp = 0xff & val; //set SP to the requested value }else if (message.type==V_STATUS) {//Position request (full open / full closed) if (!message.getBool()) { //I Use a inverted blind on the GUI bitSet(New_Cmd,1); //Fire to full close }else{ bitSet(New_Cmd,2); //Fire to full open } } blind2.UpdateRemote_cmd(New_sp,New_Cmd);//Send it to the controller }else if (message.sensor ==CHILD_ID_GAIN_BL2 ){ tmp_Faktor = message.getInt(); if(tmp_Faktor != 0) blind2.Set_Gain_PV(tmp_Faktor); gw.saveState(EPROM_GAIN_BL2,highByte( tmp_Faktor)); gw.saveState(EPROM_GAIN_BL2+1,lowByte( tmp_Faktor)); } }
Header file ""Rfm69_BlindController.h"
// Only modify this file to include // - function definitions (prototypes) // - include files // - extern variable definitions // In the appropriate section #ifndef _BlindController_H_ #define _BlindController_H_ #include "Arduino.h" //add your includes for the project BlindController here #include "MySensor.h" //end of add your includes here //Do not add code below this line #endif /* _BlindController_H_ */
-
@stedew It sounds as a great project. But I want to ask you to include the full sketch on the forum. You can do this be beginning a line with 4
characters. Then paste the code on the next line and add a new line with 4
characters.I'm personally not fund of downloading links. I downloaded viruses this way. Also people can see your sketch immediately without having to download. Thanx in advance.
-
This is part 2:
Under is the controller object it was (and is) for me an excercice how to make a modular c++ program
Al hw dependency is left out (except millis & Serial.print) everything should behave like defined on a different platform.
There is plenty room to improve this so shoot: be critic but fair wat i can improve (and how)File: "Bl_Control.cpp"
/* * Bl_Control.cpp * * Created on: 20 jul. 2016 * Author: Stefan * * * Setpoint controller up/down * Allows you to monitor and control a setpoint vs PV * 1 - use a local inputs byte to increase or decrease the position * 2 - send a setpoint/position request by a host * 3 - Set a Factor (gain) to change the ratio PV /SP * * * Version history: * 0.1 : build object class * 0.2 : Added a factor (gain) to allow a wider use and removed "magic numbers" in code * 0.3 : Fix for nasty RT-bug when gain gets higher then 131 >>integer overflow when multiply with BL_MAX_TIME_UP (250) * * * TODO: * :optimization use off variables * :implemnt auto teach sequence */ #if defined(ARDUINO) && ARDUINO >= 100 #include "Arduino.h" #else #include "WProgram.h" #endif #include "Bl_Control.h" Bl_Control::Bl_Control(){ _previoustMillis_inp_scan= millis();//init the scan clock when object is created _SP =BL_SP_MIN; //Current active SP _Remote_SP =BL_SP_MIN; //Last received SP received from the Host _Remote_CMD = t_BL_Cmd::BL_CMD_NONE;//Last received Command from the Host _Local_CMD = t_BL_Cmd::BL_CMD_NONE; ; //Last received Command from the inputs _Runtime = 0; //Time Controller is running _Error =0; //Module in error _PV =0; //Global internall Process_Value (PV) aka the actual (or estimated) position off the blind _ErrRunTmr =0; _Error=0; _tmrLocalCmddwn =t_BL_Cmd::BL_CMD_NONE; _tmrLocalCmdup =t_BL_Cmd::BL_CMD_NONE; _prev_rem_cmd= t_BL_Cmd::BL_CMD_NONE; _inputs=0; _ouputs=0; _RunCtrlCmd = t_BL_Cmd::BL_CMD_NONE; _RunCtrlprevLocCmd = t_BL_Cmd::BL_CMD_NONE; _Gain_PV =100; //Sets the controller to center gain (1/1)= 100% } /** * Get_Gain_PV : Returns the ratio (in %) _PV to _SP * inputs are : None * Chances : None * Returns : Factor (min) */ uint16_t Bl_Control::Get_Gain_PV(){ return _Gain_PV; } /** * Set_Gain_PV : Set the ratio _PV to _SP * inputs are : Newgain 10(%) min to 1000% max * Chances : _Gain_PV * Returns : None */ void Bl_Control::Set_Gain_PV (uint16_t NewGain){ if(_Gain_PV!=NewGain){ #if BL_DEBUG -1 Serial.print("N_Max_PV "); Serial.println(NewGain); #endif //We avoid float by multiply the result by 10 // 10 is 10% // 1000 is 1000% //TODO: Remove test //(Domoticz gives 1 to 100 and not 10 to 1000 NewGain = NewGain *10; if (NewGain >BL_PV_MAX ) {//Limit is 1000% NewGain = BL_PV_MAX ; }else if (NewGain > BL_PV_MIN ){ NewGain=(NewGain ); }else{ NewGain = (BL_PV_MIN ) ;//Limit is 10 } _Gain_PV=NewGain; #if BL_DEBUG -1 Serial.print("New_Gain: "); Serial.println(_Gain_PV,DEC ); #endif } } /** * bl_Process Main worker when called the period elapsed is evaluated if expired the internal routines * shall be executed * inputs are : None * * Chances : _previoustMillis_inp_scan (Time keeper) * : * Returns : None */ void Bl_Control::bl_Process (){ unsigned long currentMillis = millis(); if (currentMillis - _previoustMillis_inp_scan >= UPDATE_INPUTS_ms) { Update_Local_cmd(); Run_ctrl( ); _previoustMillis_inp_scan = currentMillis; } } /** * Update_Local_cmd - Evaluate the local input byte and update the _Local_CMD off the blind * inputs are : none * : (except the internal _inputs) * outputs are : none * : * Returns : _Local_CMD * * routine is called on a regular (timed)interval and manipulates _Local_CMD * The routine sets the cmd_state and stores the last command executed * 1° a Short (input)cmd_up/cmd_dwn gives a run to the direction requested (MAX/MIN) * 2° Long cmd_up/cmd_down gives a run state UP/DWN until release (tipping) * 3° cmd_up && cmd_dwn at same time : >> reset Error state * 4° A retrigger (when running to the max/min) >> stop is executed * */ uint8_t Bl_Control::Update_Local_cmd (void) { uint8_t tmp_cmd = _Local_CMD; //First test if there is a error reset request (both cmd_up && cmd_dwn active) if (((bitRead(_inputs, t_BL_InBits::INCR ) != 0 )&& (bitRead(_inputs, t_BL_InBits::DECR) !=0))) { _tmrLocalCmddwn =0;//Reset counters /Timers _tmrLocalCmdup = 0; _Local_CMD = t_BL_Cmd::BL_CMD_RST;//Store the last action _Error = false;// return _Local_CMD; } if (_Error) { return _Local_CMD; } // Blind up evaluation if (bitRead(_inputs,t_BL_InBits::INCR)) { if (_tmrLocalCmdup <= BL_T_Hold) { //Increase hold counter _tmrLocalCmdup++; tmp_cmd = t_BL_Cmd::BL_CMD_INC; //Sets a increase marker (tipping mode) if ( _Local_CMD == t_BL_Cmd::BL_CMD_UP) { //Repeated impulse command change to tipping mode _tmrLocalCmdup = BL_T_Hold; //set timer to holdlimit so we can stop at PB release } }else{ } } else { if ((_tmrLocalCmdup !=0) && (_tmrLocalCmdup < BL_T_Hold)){ // cmd_up is impulsed tmp_cmd = t_BL_Cmd::BL_CMD_UP; }else{ // button was hold longer then T_Hold (Tipping) but now released if (_tmrLocalCmdup >=BL_T_Hold) tmp_cmd = t_BL_Cmd::BL_CMD_STOP; } // When button is released reset the counter _tmrLocalCmdup =0; } // Blind Down evaluation if (bitRead(_inputs, t_BL_InBits::DECR )) { //st_tmrhld_Localup=0; // When we go down up is reset if (_tmrLocalCmddwn <= BL_T_Hold){ _tmrLocalCmddwn++; //Increase hold tmp_cmd = t_BL_Cmd::BL_CMD_DEC; if ( _Local_CMD == t_BL_Cmd::BL_CMD_DWN) {//Repeated impulse command change to tipping mode _tmrLocalCmddwn = BL_T_Hold; //set timer to holdlimit so we can stop at PB release } } } else { if ((_tmrLocalCmddwn !=0) && (_tmrLocalCmddwn < BL_T_Hold)){ // cmd_dwn is impulsed tmp_cmd = t_BL_Cmd::BL_CMD_DWN; }else{ // button was hold longer then T_Hold (tipping) but now released >>release Tip if (_tmrLocalCmddwn >=BL_T_Hold) tmp_cmd = t_BL_Cmd::BL_CMD_STOP; } // When button is released reset the counter _tmrLocalCmddwn =0; } if (tmp_cmd != _Local_CMD) { _Local_CMD = tmp_cmd; } return _Local_CMD; } /** * bl_GetOutputs Returns the current output bits to the caller * inputs are : None * * Chances : None * : * Returns : Output bits */ uint8_t Bl_Control::bl_GetOutputs (void) { return _ouputs; } /** * UpdateRemote_cmd - Evaluate the messages from the host (here domoticsz) * inputs are : MyMessage * * Chances : _Remote_SP, * : * Returns : remote_Commandstate * * routine is called when a message from the host is received * The routine sets the cmd_stat and stores the last command if changed * The routine interprets the message and send a open/close or run to new setpoint */ uint8_t Bl_Control::UpdateRemote_cmd (uint8_t Sp, uint8_t cmd_Type ) { uint8_t tmp_cmd= _Remote_CMD;//Current command uint8_t tmp_SP ;// Current Setpoint tmp_SP = _Remote_SP;//Store the current SP if (bitRead(cmd_Type ,0)) { // This is a SP change request if (_Remote_SP != Sp) {//Is SP from host different to current one? #if BL_DEBUG -1 Serial.println("BL_RM"); Serial.print("New SP: "); Serial.println(val); #endif tmp_SP = Sp; //set SP to the requested value tmp_cmd = BL_CMD_RUN; }else {//Nothing to do except change local static tmp_cmd = BL_CMD_NONE;//reception off same as previous SP } } else if (bitRead(cmd_Type ,1)|| bitRead(cmd_Type ,2)) {//Position request (full open / full closed) if (bitRead(cmd_Type ,1)) { //I Use a inverted blind on the GUI if (_Remote_CMD ==BL_CMD_DWN ) {//Re-fire in Run will trigger a stop tmp_cmd = BL_CMD_STOP;//Second push to full position >> stop #if BL_DEBUG -1 Serial.println("BL STP"); #endif }else{ tmp_SP = BL_SP_MIN ;//adjust SP to min (close) tmp_cmd= BL_CMD_DWN; #if BL_DEBUG -1 Serial.println("BL_DWN"); #endif } } else { if (_Remote_CMD ==BL_CMD_UP) { tmp_cmd = BL_CMD_STOP;//Second push to full position >> stop #if BL_DEBUG -1 Serial.println("BL STP"); #endif } else { tmp_SP = BL_SP_MAX ;//adjust SP to max tmp_cmd= BL_CMD_UP; #if BL_DEBUG -1 Serial.println("BL_UP"); #endif } } } if (_Remote_CMD == tmp_cmd){ if (_Remote_SP != tmp_SP) { _Remote_SP = tmp_SP; //Overwrite back (modified) SP to caller _Remote_CMD = BL_CMD_RUN; //Set Run State to start return _Remote_CMD; } else { return BL_CMD_NONE; //Nothing changed } }else{ _Remote_SP = tmp_SP; //write back (modified) SP to caller _Remote_CMD = tmp_cmd; return _Remote_CMD; } } /** * SetInputs : changes the local inputbits (input command word) * inputs are : inc * : dec * : RunCurrent * * outputs are : none (manipulates the internal input byte) * Returns : none * * call this routine to reflect input changes to the ( internal )input controller */ void Bl_Control::bl_SetInputs(uint8_t inc , uint8_t dec, uint8_t RunCurrent ){ if (inc) bitSet(_inputs,t_BL_InBits::INCR); else bitClear(_inputs,t_BL_InBits::INCR); if (dec) bitSet(_inputs,t_BL_InBits::DECR); else bitClear(_inputs,t_BL_InBits::DECR); if (RunCurrent) bitSet(_inputs,t_BL_InBits::CURSNS); else bitClear(_inputs,t_BL_InBits::CURSNS); } /** * bl_RunErrorLed :Give some feedback for the user * inputs are : inc * : dec * : RunCurrent * * outputs are : none * : * Returns : State for the error/run Led * * call this routine for getting the led state (In Run slow blink / In error fast blink) * */ uint8_t Bl_Control::bl_RunErrorLed(void){ if (_Error){ if (bitRead(_ErrRunTmr,1)){ return true; }else{ return false; } }else{ if ((bitRead(_ouputs,t_BL_OutBits::BL_RN_UP))||bitRead(_ouputs,t_BL_OutBits::BL_RN_DWN) ) { if (bitRead(_Runtime,2)){ return true; }else{ return false; } return true; }else { if (_ErrRunTmr) _ErrRunTmr=0; return false; } } } /** * Run_ctrl - main worker controls the movement off the blind * inputs are : _Error, _SP, _PV (internal) * : * outputs are : none * : * Returns : _ouputs (for I/0) * * routine is called on a regular (timed)interval and is controlled by the inputs byte(s) * Commands are stored and executed if a change occurs compared to previous (call) * Also an error will cause a stop immediately * Total runtime is controlled and generates a error if exceeded >> in Error no remote commands are accepted only the local reset cmd * */ uint8_t Bl_Control::Run_ctrl (void){ uint32_t tmp_l1 = 0;//Local scratch var //First Test for a pending error if (_Error ) { _RunCtrlCmd=BL_RN_ERR; _Runtime =0;//Stop internal timer if (_ErrRunTmr == 0xff) { _ErrRunTmr = 1; } else { _ErrRunTmr++; } return _RunCtrlCmd;//Exit here no need to continue }else{ _ErrRunTmr=0; } //TODO: interlocking the remote /local should be optimized //No errors: evaluate witch command must be executed check for (new) local/remote command if (_Local_CMD !=_RunCtrlprevLocCmd ) { //Local command different from current run state store in _RunCtrlCmd _RunCtrlCmd = _Local_CMD; //Make the new stat the active one #if BL_DEBUG-1 Serial.print("BL_LC"); Serial.println(_RunCtrlCmd); #endif } else if (_Remote_CMD != _prev_rem_cmd) { #if BL_DEBUG-1 Serial.println("BL_RM"); #endif _RunCtrlCmd =_Remote_CMD;//copy the remote cmd to local }else{ //Leave it there is no new cmd } //Setpoint manipulator/Controller (depends on (new)input command switch (_RunCtrlCmd) { case BL_CMD_STOP: _SP = _PV;//Assign the actual PV to the internal SP break; case BL_CMD_INC ://increase Setpoint //only increase if SP is smaller then max PV if (_SP < (_Gain_PV)){ _SP++ ; if (_Runtime ==0) _Runtime =1; } break; case BL_CMD_UP://Setpoint to max if (_SP != _Gain_PV) { _SP = _Gain_PV ; if (_Runtime ==0) _Runtime =1; //Run ...Run } break; case BL_CMD_DEC ://Setpoint decrease (tipping) if (_SP != BL_SP_MIN){ if(_SP > (BL_SP_MIN) ){ _SP-- ; if (_Runtime ==0) _Runtime =1; } #if BL_DEBUG-1 Serial.print("DSP: "); Serial.println(_SP,DEC ); #endif } break; case BL_CMD_DWN ://Setpoint to min if (_SP != BL_SP_MIN) { _SP = BL_SP_MIN; if (_Runtime ==0) _Runtime =1; } break; case BL_CMD_RUN : //Setpoint received from host tmp_l1 =(_Remote_SP * _Gain_PV /BL_SP_MAX); //First calculate the scaled setpoint from the host if (_SP != tmp_l1){ //is this a different setpoint ? _SP = tmp_l1; #if BL_DEBUG-1 Serial.print("Sp: "); Serial.println(_SP,DEC); Serial.print("_PV: "); Serial.println(_PV,DEC ); #endif if (_Runtime ==0) _Runtime =1; } break; default: break; } //Position (PV) Generator there is no feedback position so it is simulated by counters/timers if (_SP ==( _PV) ){ bitClear(_ouputs, t_BL_OutBits::BL_RN_DWN); bitClear(_ouputs, t_BL_OutBits::BL_RN_UP); _Runtime =0;//Stop }else{ if ((_SP ) > (_PV) ){ bitClear(_ouputs, t_BL_OutBits::BL_RN_DWN); bitSet(_ouputs, t_BL_OutBits::BL_RN_UP); if ((_PV < _Gain_PV) && (_Runtime !=0))_PV++; }else{ //(_SP < _PV) bitClear(_ouputs, t_BL_OutBits::BL_RN_UP); bitSet(_ouputs, t_BL_OutBits::BL_RN_DWN); if (_PV>=BL_SP_MIN) _PV--; } } // Feedback that there is still current flowing if (bitRead(_inputs,t_BL_InBits::CURSNS )){ // We still are driving the outputs increase run time if (_Runtime){ // FIX 0.3 : the line beneath uses a cast (uint32_t) on a constants omitting this will give you a few hours joy... tmp_l1 =(uint32_t)BL_MAX_TIME_UP * _Gain_PV / (uint32_t)BL_SP_MAX ; //First calculate the max (scaled) Run time #if BL_DEBUG -1 if (_Runtime ==2) { Serial.print("Max_PV: "); Serial.println(tmp_l1,DEC); } #endif if ( _Runtime < tmp_l1)_Runtime ++; //If not overtime increase timer #if BL_DEBUG -1 Serial.print("RT: "); Serial.println(_Runtime,DEC); Serial.print("PV: "); Serial.println(_PV,DEC); #endif } }else{ //Current is dropped >> Motor off or (in this case) end limit reached if (( bitRead(_ouputs, t_BL_OutBits::BL_RN_UP ) ||bitRead(_ouputs, t_BL_OutBits::BL_RN_DWN )) && (_Runtime>WAITFORCURRENT) ) { //Todo: when testing this with feedback maybe this can be optimized to _PV = _SP; (as this will probably be true ) //When the current is gone we have reached a end limit if ( bitRead(_ouputs, t_BL_OutBits::BL_RN_UP )){ bitClear(_ouputs, t_BL_OutBits::BL_RN_UP); _PV = _Gain_PV;//Reached the Max limit ...inform the host } if ( bitRead(_ouputs, t_BL_OutBits::BL_RN_DWN )){ bitClear(_ouputs, t_BL_OutBits::BL_RN_DWN); _PV = BL_SP_MIN;//Reached the min limit ...inform the host } //The current is gone but still driving UP/DOWN stop it (should be redundant here) bitClear(_ouputs, t_BL_OutBits::BL_RN_DWN); bitClear(_ouputs, t_BL_OutBits::BL_RN_UP); } } // Guard the max run time (Up/down time) if (_Runtime){ // FIX 0.3 : the line beneath uses a cast (uint32_t) on a constants omitting this will give you a few hours joy... tmp_l1 = (uint32_t) BL_MAX_TIME_UP *_Gain_PV / (uint32_t)BL_SP_MAX;//First calculate the max (scaled) Run time if ( _Runtime >= tmp_l1) { bitClear(_ouputs, t_BL_OutBits::BL_RN_DWN); //Overrun (blokking?) >> stop bitClear(_ouputs, t_BL_OutBits::BL_RN_UP); bitSet(_ouputs, t_BL_OutBits::BL_RN_ERR); _Error = true; #if BL_DEBUG-1 Serial.print("RT: "); Serial.print(_Runtime); Serial.print(" HL: "); Serial.print(tmp_l1,DEC); Serial.print(" G: "); Serial.print(_Gain_PV); Serial.println(" TIME_OUT "); #endif } } if (_Local_CMD !=_RunCtrlprevLocCmd ) { //Update the command switcher for next entry _RunCtrlprevLocCmd = _Local_CMD; }else if (_Remote_CMD !=_prev_rem_cmd ){ _prev_rem_cmd = _Remote_CMD; } return _ouputs;// return the Run_Result } /** * Get_PV : Returns the current ProcessValue (actual state) or feedback in % * inputs are : None * Changes : None * : * Returns : _PV * */ uint8_t Bl_Control::Get_PV (void){ return ((_PV*BL_SP_MAX)/_Gain_PV); }
At last the header File "Bl_Control.h"
// - function definitions (prototypes) // - include files // - extern variable definitions // In the appropriate section #ifndef _BL_Control_H_ #define _BL_Control_H_ #include "Arduino.h" //add your includes for the project BlindController here #include <inttypes.h> //end of add your includes here //add your function definitions for the project BlindController here #define BL_DEBUG 1 //Enable/disable debugging msg (to serial) #define UPDATE_INPUTS_ms 50UL // scan time inputs for change #define WAITFORCURRENT 3 //Allow the current to come up when outputs activate #define BL_T_Hold 5 // This is the number off scans before a button is considered as hold (Tipping) #define BL_SP_MIN 0x00 // SP_LL no comment.... #define BL_SP_MAX 100 // SP_H This value is the value considered as 100% output #define BL_MAX_TIME_UP 250 //With a scan time off 50mSec the this give only 12Sec (1000mSec/50 * t_Scan_SeC < 0xFF) //on the other hand there is also a factor ( #define BL_PV_MIN 10 // Min gain percentage (10%) #define BL_PV_MAX 1000UL // Min gain percentage (1000%) #if BL_MAX_TIME_UP <= BL_SP_MAX //When changing parameters test if the SP is reachable without error #error "Attention !! you can't reach the max allowed SP val in one run with this setting!" #endif typedef enum BL_OutBits { BL_RN_UP, BL_RN_DWN, BL_RN_ERR}t_BL_OutBits; typedef enum BL_Cmd {BL_CMD_NONE, BL_CMD_STOP,BL_CMD_INC, BL_CMD_UP, BL_CMD_DEC ,BL_CMD_DWN,BL_CMD_RST ,BL_CMD_RUN} t_BL_Cmd ; typedef enum BL_InBits{ INCR, DECR, CURSNS}t_BL_InBits; class Bl_Control { public: // Create an instance of the BlindController Bl_Control(); // call this to run the timers void bl_Process (void); //Get the outputbits uint8_t bl_GetOutputs (void); //Update info from the host (updates the remote cmd bits) uint8_t UpdateRemote_cmd (uint8_t Sp, uint8_t cmd_Type ); //Set the inputs void bl_SetInputs (uint8_t inc , uint8_t dec, uint8_t RunCurrent ); uint8_t bl_RunErrorLed (void); uint8_t Get_PV (void); uint16_t Get_Gain_PV (); void Set_Gain_PV (uint16_t fakt); private: //SP and PV updater uint8_t Run_ctrl (void); // evaluate the inputcmd (timers) uint8_t Update_Local_cmd (void); protected: uint8_t _tmrLocalCmdup; //Timer off the PB UP uint8_t _tmrLocalCmddwn; //Timer off the PB DWN uint8_t _ErrRunTmr; //Timer off the error/Run uint8_t _RunCtrlCmd; //the active command off Controller uint8_t _RunCtrlprevLocCmd ; //Store the previous local state uint8_t _prev_rem_cmd; //Store the previous state at end off routine uint8_t _inputs; //InputBits uint8_t _ouputs; //OutputBits //timer logic to schedule updates ,cmd ,inputs,... unsigned long _previoustMillis_inp_scan ; uint8_t _Remote_SP; //Last received SP received from the Host uint8_t _Remote_CMD ; //Last received Command from the Host uint8_t _Local_CMD ; //Last received Command from the inputs uint16_t _Runtime ; //Time Controller is running uint16_t _SP ; //internal Setpoint uint16_t _PV; //Global Process_Value (PV) aka the actual (or estimated) position uint8_t _Error; //Module in error uint16_t _Gain_PV ; //Factor for _PV to reach the setpoint (in %) }; //Do not add code below this line #endif /* _BL_Control_H_ */
-
Hello TheoL i think you are right, but it is hard to copy/past a project to a forum. But why not i changed the post and now almost everybody can see how crapy code i write
Regards,
Stefan.
-
@stedew Thank you for embedding the code on the forum.
I'm not sure what your question is, but I took a quick peek at the code. I haven't examined everything thoroughly. But here's something I noticed. I can tell you already know about C(++).
pinMode(DI_BL1_UP, INPUT_PULLUP); pinMode(DI_BL1_DWN, INPUT_PULLUP); pinMode(AI_BL1_SENS,INPUT_PULLUP); //Set outputs to safe state and init digitalWrite( DO_BL1_MOTOR_UP,MOTOR_OFF); digitalWrite( DO_BL1_MOTOR_DWN,MOTOR_OFF); digitalWrite(DO_BL1_ERR_RUN ,MOTOR_OFF); pinMode(DO_BL1_MOTOR_UP, OUTPUT); pinMode(DO_BL1_MOTOR_DWN, OUTPUT); pinMode(DO_BL1_ERR_RUN, OUTPUT); //BLIND2 //TODO: Test extra controller pinMode(DI_BL2_UP, INPUT_PULLUP); pinMode(DI_BL2_DWN, INPUT_PULLUP); //pinMode(AI_BL2_SENS,INPUT_PULLUP); //Set outputs to safe state and init digitalWrite( DO_BL2_MOTOR_UP,MOTOR_OFF); digitalWrite( DO_BL2_MOTOR_DWN,MOTOR_OFF); digitalWrite(DO_BL2_ERR_RUN ,MOTOR_OFF); pinMode(DO_BL2_MOTOR_UP, OUTPUT); pinMode(DO_BL2_MOTOR_DWN, OUTPUT); pinMode(DO_BL2_ERR_RUN, OUTPUT);
If I remember correctly, the Arduino needs the pinMode state before you write to the pin. So it's better to turn them into this order:
pinMode(DI_BL1_UP, INPUT_PULLUP); pinMode(DI_BL1_DWN, INPUT_PULLUP); pinMode(AI_BL1_SENS,INPUT_PULLUP); //Set outputs to safe state and init pinMode(DO_BL1_MOTOR_UP, OUTPUT); pinMode(DO_BL1_MOTOR_DWN, OUTPUT); pinMode(DO_BL1_ERR_RUN, OUTPUT); digitalWrite( DO_BL1_MOTOR_UP,MOTOR_OFF); digitalWrite( DO_BL1_MOTOR_DWN,MOTOR_OFF); digitalWrite(DO_BL1_ERR_RUN ,MOTOR_OFF); //BLIND2 //TODO: Test extra controller pinMode(DI_BL2_UP, INPUT_PULLUP); pinMode(DI_BL2_DWN, INPUT_PULLUP); //pinMode(AI_BL2_SENS,INPUT_PULLUP); //Set outputs to safe state and init pinMode(DO_BL2_MOTOR_UP, OUTPUT); pinMode(DO_BL2_MOTOR_DWN, OUTPUT); pinMode(DO_BL2_ERR_RUN, OUTPUT); digitalWrite( DO_BL2_MOTOR_UP,MOTOR_OFF); digitalWrite( DO_BL2_MOTOR_DWN,MOTOR_OFF); digitalWrite(DO_BL2_ERR_RUN ,MOTOR_OFF);
There's als a S_COVER cover type, which looks like a sensor type you've implemented. But I'm not sure if this type is supported by controllers.
I really like what you did. But I'm not sure if it works. The .h file seems to lack the class definition. And it's been a while since a learned C++, but as I've always have learned the class definition needs to be put in a .h file.
Also very curious about your decision not to include the logic for controlling the motors and pin initialization in the class itself but to keep it in the main sketch? It's not wrong but why do all the work for creating a class without putting the logic where it belongs (in the class).
I do love OO programming. But for the Arduino I only use plain C. It consumes less memory.
-
Again right.. but the class definition is in the BL_Control.h file (line 47) The reason not to put the logic is that i yust want some sceleton for this kind of physical controller now i don' t have to include write and reads in the class and i can re-use it maybe for a PB dimmer. That off the declaration with the pinmodes is a habbit from the plain C period and assembler adventures with the AVR. There i set my outputs to a safe state before setting the DDR(x) register. As far as i assume the DDR is only relevant when you read the input (or want to do a logic operation on the input/output state. And it is overkill to write this in C++. But for me i consider it as a good excercise. And no i don't have a question I only wanted it to publish so others can steal maybe a idea Cheers,