Cloud Wifi Attachment

Expansion modules and attachments
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

I could happen. I don't think there is anything out there yet to leverage so we would need to replicate the STK500v2 protocol on the cloud wifi attachment to be able to upload remotely.
Roberto.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

I'm not sure what is going on, but I've had the cloud wifi app for a couple months now and it has started going to a solid green light from the blue connected light. Nothing has changed in setup or placement of it or the wifi router. I check the pins and everything is fine. It's on the side of the tank, so no moisture to speak of. I've been having to unplug it and re plug it back in. Sometime I have to do that a couple times before it reconnects. It is 12-15' away from the router
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

Do you have a computer that you can connect to the module and leave it logging to see what happens?
If so, connect the usb cable to the cloud wifi module and open Arduino. Then go to Tools->Serial monitor.
You should start seeing the log.
Roberto.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

I will do that and post back. What I noticed is interesting is when it is connected after a while I go and check via the U-App and then it goes to green and eventually reconnects
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

Code: Select all

PH:809
PH:808
PWMD:0
PWMA:38
PWMD:38
PWMA:0
PWMD:0
PWMA:38
PWMD:38
PWMA:0
PWMD:0
PWMA:38
PH:809
PH:808
PWMD:38
PWMA:0
PWMD:0
PWMA:38
PWMD:38
PWMA:0
PWMD:0
PWMA:38
PH:809
PH:808
PWMD:38
PWMA:0
PWMD:0
PH:809
PH:808
Connecting to Portal Server
Connected
Disconnecting from Portal Server
PWMD:38
PH:809
PH:808
T1:786
T1:784
T1:786
T1:784
all:0
ATOLOW:0
ATOHIGH:0
EM:0
EM1:2
REM:0
BID:1
AF:0
SF:0
PWMD:38
PWMA:0
PWMDO:255
PWMAO:255
R1:0
ROFF1:0
RON1:0
R2:0
ROFF2:0
RON2:0
R3:0
ROFF3:0
RON3:0
R4:0
ROFF4:0
RON4:0
R5:0
ROFF5:0
RON5:0
R6:0
ROFF6:0
RON6:0
R7:0
ROFF7:0
RON7:0
R8:0
ROFF8:0
RON8:0
ALARM:0
PWMD2:0
PWMA2:0
PWMD2O:0
PWMA2O:0
WL:0
WL1:0
WL2:0
WL3:0
WL4:0
HUM:0
DCM:5
DCS:38
DCD:50
DCT:20
PWME0:0
PWME1:0
PWME2:0
PWME3:0
PWME4:0
PWME5:0
PWME0O:0
PWME1O:0
PWME2O:0
PWME3O:0
PWME4O:0
PWME5O:0
AIW:0
AIB:0
AIRB:0
RFM:0
RFS:0
RFD:0
RFW:0
RFRB:0
RFR:0
RFG:0
RFB:0
RFI:0
RFWO:0
RFRBO:0
RFRO:0
RFGO:0
RFBO:0
RFIO:0
IO:0
LEAK:0
C0:0
C1:0
C2:0
C3:0
C4:0
C5:0
C6:0
C7:0
R:183
ROFF:66
RON:0
T1:784
T2:0
T3:0
PH:808
ORP:0
SAL:0
PHE:0
PAR:0
CEXP0:0
CEXP1:0
CEXP2:0
CEXP3:0
CEXP4:0
CEXP5:0
CEXP6:0
CEXP7:0
Connecting to Portal Server
Connected
Disconnecting from Portal Server
T1:786
T1:784
PWMA:20
Connecting to Portal Server
Connected
Disconnecting from Portal Server
T1:786
T1:784
T1:786
T1:784
PWMA:21
PWMA:22
PWMA:23
PWMA:24
PWMA:25
PWMA:26
PWMA:27
PWMA:28
PWMA:29
Connecting to Portal Server
Connected
PWMA:30
Disconnecting from Portal Server
PWMA:31
T1:786
T1:784
PWMA:32
PWMA:33
PWMA:34
T1:786
T1:784
PWMA:35
T1:786
T1:784
PWMA:36
T1:786
T1:784
PWMA:37
T1:786
T1:784
T1:786
T1:784
PWMA:38
PWMA:37
Connecting to Portal Server
Connected
T1:786
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
MQTT Connecting...
MQTT failed
Connecting to Portal Server
Failed to connect
Disconnecting from Portal Server
MQTT Connecting... 
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

It does seem that it is loosing the connection and can't reconnect again for some reason.
Let me see if I can find anything with the firmware.
Roberto.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

Thank you Rimai. Most of today it has not been connected, just blinking green. If it makes any difference I had used the webwizard to program the reef angel plus.
LucianoB
Posts: 23
Joined: Fri Mar 11, 2016 4:39 pm

Re: Cloud Wifi Attachment

Post by LucianoB »

slm222 wrote:Thank you Rimai. Most of today it has not been connected, just blinking green. If it makes any difference I had used the webwizard to program the reef angel plus.
Hi. What Router are you using?
I has a lot of the problems with one mikrotik Router. I changed that so everything are working fine.. if you can only test with another Router.

Regards from Argentina.
Cheers

Enviado desde mi Nexus 5 mediante Tapatalk
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

See if this code makes any difference.

Code: Select all

#include <SPI.h>
#include <WiFi101.h>
#include <PubSubClient.h>
#include <RA_CustomSettings.h>

//char ssid[32];
//char pass[32];
//char username[16];
//char password[16];

unsigned long client_timeout = millis();
unsigned long wifi_connection = millis();
unsigned long serial_timeout = millis();
unsigned long led_blink = millis();
unsigned long MQTTReconnectmillis = millis();

boolean LED_status = false;
boolean PortalConnection = false;
boolean power_status = true;
boolean data_ready = false;
int status = WL_IDLE_STATUS;
int connection_counter=0;
WiFiServer server(2000);
WiFiClient client;
WiFiClient portalclient;
WiFiClient mqttclient;

void mqttcallback(char* topic, byte* payload, unsigned int length);

PubSubClient CloudClient(MQTTServer, MQTTPORT, mqttcallback, mqttclient);

String currentLine = "";                // make a String to hold incoming data from the client

void setup() {
  Serial.begin(57600);      // initialize serial communication
  Serial1.begin(57600);      // initialize serial communication
  pinMode(5, OUTPUT);      // set the LED pin mode
  pinMode(6, OUTPUT);      // set the LED pin mode
  pinMode(7, OUTPUT);      // set the LED pin mode
  wdt_initialize();
  wdt_enable();
}

void loop() {
  wdt_reset();
  status = WiFi.status();
  while ( status != WL_CONNECTED) {
    digitalWrite(5, false);
    digitalWrite(6, true);
    digitalWrite(7, false);
    if (millis() - wifi_connection > 1000)
    {
      wifi_connection = millis();
      Serial.print("Attempting to connect to Network named: ");
      Serial.println(WIFI_SSID);                   // print the network name (SSID);
      if (WiFi.begin(WIFI_SSID, WIFI_PASS) == WL_CONNECTED)
      {
        status = WiFi.status();
        server.begin();                           // start the web server on port 2000
        printWifiStatus();                        // you're connected now, so print out the status
        digitalWrite(6, false);
        digitalWrite(7, true);
      }
      Serial.println("Not connected...");
    }
    wdt_reset();
  }

  client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println(F("new incoming client"));           // print a message out the serial port
    client_timeout = millis();
    while (client.connected()) {            // loop while the client's connected
      if (millis() - client_timeout > 2000) client.stop();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        Serial1.write(c);                   // send to Reef Angel
      }
      if (Serial1.available())
      {
        char c = Serial1.read();
        //Serial.write(c);
        currentLine += c;
        serial_timeout = millis();
        if (currentLine.length() == 255) {
          SendData();
        }
      }
      if (millis() - serial_timeout > 400)
      {
        if (currentLine.length() > 0) {
          SendData();
          Serial.println();
          client.println();
        }
      }
      if (millis() - led_blink > 50)
      {
        led_blink = millis();
        LED_status = !LED_status;
        digitalWrite(6, LED_status);
      }
    }
    // close the connection:
    client.stop();
    Serial.println(F("incoming client disconnected"));
    digitalWrite(6, false);
  }
  else
  {
    while (Serial1.available())
    {
      char c = Serial1.read();
      currentLine += c;
      if(c==10) data_ready=true;
      serial_timeout = millis();
      digitalWrite(6, true);
      if (millis() - serial_timeout > 200 || data_ready)
      {
        if (currentLine.length() > 0) 
        {
          data_ready=false;
          serial_timeout = millis();
          //        Serial.print(currentLine);
          if (currentLine.startsWith("GET"))
          {
            PortalConnection = true;
            Serial.println(F("Connecting to Portal Server"));
            if (portalclient.connect(PortalServer, 80)) {
              Serial.println(F("Connected"));
              // Make a HTTP request:
              portalclient.print(currentLine);
              portalclient.println(F(" HTTP/1.1"));
              portalclient.println(F("Host: forum.reefangel.com"));
              portalclient.println(F("Connection: close"));
              portalclient.println();
            }
            else
            {
              Serial.println(F("Failed to connect"));
            }
          }
          if (currentLine.startsWith("CLOUD:"))
          {
            //Serial.print(currentLine);
            if (currentLine.length() < 32)
            {
              if (CloudClient.connected())
              {
                //Serial.println(F("Publishing "));
                currentLine.replace("CLOUD:", "");
                Serial.print(currentLine);
                char pub_buffer[sizeof(CLOUD_USERNAME) + 5];
                sprintf(pub_buffer, "%s/out", CLOUD_USERNAME);
                char pub_msg[currentLine.length() + 1];
                currentLine.toCharArray(pub_msg, currentLine.length() + 1);
                CloudClient.publish(pub_buffer, pub_msg);
              }
            }
            else
            {
              Serial.print(F("Error on incoming data: "));
              Serial.println(currentLine.length());
            }
          }
          currentLine = "";
          digitalWrite(6, false);
        }
      }
    }
    digitalWrite(6, false);    
    while (portalclient.available()) {
      char c = portalclient.read();
      //      Serial.write(c);
    }
    if (PortalConnection && !portalclient.connected()) {
      PortalConnection = false;
      //      Serial.println();
      Serial.println(F("Disconnecting from Portal Server"));
      digitalWrite(6, false);
      portalclient.stop();
    }
  }

  CloudClient.loop();
  if (millis() - MQTTReconnectmillis > 5000)
  {
    if (!CloudClient.connected())
    {
      char sub_buffer[sizeof(CLOUD_USERNAME) + 6];
      MQTTReconnectmillis = millis();
      digitalWrite(6, true);
      Serial.println(F("MQTT Connecting..."));
      Serial.print(F("Attemp: "));
      Serial.println(++connection_counter);
      if (connection_counter>5)
      {
        Serial.println(F("Unable to connect. Rebooting..."));
        digitalWrite(5, true);
        digitalWrite(6, false);
        digitalWrite(7, false);        
        while(1);
      }
      sprintf(sub_buffer, "RA-%s", CLOUD_USERNAME);
      if (CloudClient.connect(sub_buffer, CLOUD_USERNAME, CLOUD_PASSWORD))
      {
        sprintf(sub_buffer, "%s/in/#", CLOUD_USERNAME);
        Serial.println(F("MQTT succeeded"));
        CloudClient.subscribe(sub_buffer);
      }
      else
      {
        MQTTReconnectmillis = millis();
        Serial.println(F("MQTT failed"));
        CloudClient.disconnect();
      }
      digitalWrite(6, false);
    }
  }
  if (analogRead(0) < 600 && power_status)
  {
    power_status = false;
  }
  if (analogRead(0) > 600 && !power_status)
  {
    power_status = true;
  }
}

void SendData()
{
  serial_timeout = millis();
  //  Serial.print(currentLine);
  client.print(currentLine);
  currentLine = "";
}

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

void mqttcallback(char* topic, byte* payload, unsigned int length)
{
  Serial1.print("cloud:");
  for (int a=0;a<length;a++)
  {
    Serial1.write(payload[a]);
    Serial.write(payload[a]);
  }
  Serial1.print(" ");
  Serial.println();
}

void wdt_initialize()
{
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(4);
  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(2) |
                      GCLK_GENCTRL_GENEN |
                      GCLK_GENCTRL_SRC_OSCULP32K |
                      GCLK_GENCTRL_DIVSEL;
  while (GCLK->STATUS.bit.SYNCBUSY);  // Syncronize write to GENCTRL reg.
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT |
                      GCLK_CLKCTRL_CLKEN |
                      GCLK_CLKCTRL_GEN_GCLK2;
}

void wdt_enable()
{
  WDT->CTRL.reg &= ~WDT_CTRL_ENABLE;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CTRL reg.
  WDT->CONFIG.reg = WDT_CONFIG_PER(0xA);  // 0xA = 8192 ms
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CONFIG reg.
  WDT->INTENCLR.reg |= WDT_INTENCLR_EW;
  WDT->CTRL.reg |= WDT_CTRL_ENABLE;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CTRL reg.
}

void wdt_disable()
{
  WDT->CTRL.reg &= ~WDT_CTRL_ENABLE;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CTRL reg.
}

void wdt_reset()
{
  WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CLEAR reg.
}

// RA_STRING1=U2FsdGVkX19hO6KSSw/SEWReqPKC34stYAL4B8vahEY=
// RA_STRING2=U2FsdGVkX1+BPena6vAB/qszbCcvez8FgwKcXXxBbYg=
// RA_STRING3=test
Be aware that you will need to copy and paste the last 3 lines of the code to match whatever you have from your own code that you generated from the webwizard.
Roberto.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

LucianoB wrote:
slm222 wrote:Thank you Rimai. Most of today it has not been connected, just blinking green. If it makes any difference I had used the webwizard to program the reef angel plus.
Hi. What Router are you using?
I has a lot of the problems with one mikrotik Router. I changed that so everything are working fine.. if you can only test with another Router.

Regards from Argentina.
Cheers

Enviado desde mi Nexus 5 mediante Tapatalk
I am using an ASUS router, but no changes have been made and the intermittent issues started out of now where. I have a Linksys AC2400 coming in to try that as well.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

rimai wrote:See if this code makes any difference.
I will add this into my cloud device tonight. Thank you

This is your rewritten code with my info at the bottom i will add

Code: Select all

#include <SPI.h>
#include <WiFi101.h>
#include <PubSubClient.h>
#include <RA_CustomSettings.h>

//char ssid[32];
//char pass[32];
//char username[16];
//char password[16];

unsigned long client_timeout = millis();
unsigned long wifi_connection = millis();
unsigned long serial_timeout = millis();
unsigned long led_blink = millis();
unsigned long MQTTReconnectmillis = millis();

boolean LED_status = false;
boolean PortalConnection = false;
boolean power_status = true;
boolean data_ready = false;
int status = WL_IDLE_STATUS;
int connection_counter=0;
WiFiServer server(2000);
WiFiClient client;
WiFiClient portalclient;
WiFiClient mqttclient;

void mqttcallback(char* topic, byte* payload, unsigned int length);

PubSubClient CloudClient(MQTTServer, MQTTPORT, mqttcallback, mqttclient);

String currentLine = "";                // make a String to hold incoming data from the client

void setup() {
  Serial.begin(57600);      // initialize serial communication
  Serial1.begin(57600);      // initialize serial communication
  pinMode(5, OUTPUT);      // set the LED pin mode
  pinMode(6, OUTPUT);      // set the LED pin mode
  pinMode(7, OUTPUT);      // set the LED pin mode
  wdt_initialize();
  wdt_enable();
}

void loop() {
  wdt_reset();
  status = WiFi.status();
  while ( status != WL_CONNECTED) {
    digitalWrite(5, false);
    digitalWrite(6, true);
    digitalWrite(7, false);
    if (millis() - wifi_connection > 1000)
    {
      wifi_connection = millis();
      Serial.print("Attempting to connect to Network named: ");
      Serial.println(WIFI_SSID);                   // print the network name (SSID);
      if (WiFi.begin(WIFI_SSID, WIFI_PASS) == WL_CONNECTED)
      {
        status = WiFi.status();
        server.begin();                           // start the web server on port 2000
        printWifiStatus();                        // you're connected now, so print out the status
        digitalWrite(6, false);
        digitalWrite(7, true);
      }
      Serial.println("Not connected...");
    }
    wdt_reset();
  }

  client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println(F("new incoming client"));           // print a message out the serial port
    client_timeout = millis();
    while (client.connected()) {            // loop while the client's connected
      if (millis() - client_timeout > 2000) client.stop();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        Serial1.write(c);                   // send to Reef Angel
      }
      if (Serial1.available())
      {
        char c = Serial1.read();
        //Serial.write(c);
        currentLine += c;
        serial_timeout = millis();
        if (currentLine.length() == 255) {
          SendData();
        }
      }
      if (millis() - serial_timeout > 400)
      {
        if (currentLine.length() > 0) {
          SendData();
          Serial.println();
          client.println();
        }
      }
      if (millis() - led_blink > 50)
      {
        led_blink = millis();
        LED_status = !LED_status;
        digitalWrite(6, LED_status);
      }
    }
    // close the connection:
    client.stop();
    Serial.println(F("incoming client disconnected"));
    digitalWrite(6, false);
  }
  else
  {
    while (Serial1.available())
    {
      char c = Serial1.read();
      currentLine += c;
      if(c==10) data_ready=true;
      serial_timeout = millis();
      digitalWrite(6, true);
      if (millis() - serial_timeout > 200 || data_ready)
      {
        if (currentLine.length() > 0) 
        {
          data_ready=false;
          serial_timeout = millis();
          //        Serial.print(currentLine);
          if (currentLine.startsWith("GET"))
          {
            PortalConnection = true;
            Serial.println(F("Connecting to Portal Server"));
            if (portalclient.connect(PortalServer, 80)) {
              Serial.println(F("Connected"));
              // Make a HTTP request:
              portalclient.print(currentLine);
              portalclient.println(F(" HTTP/1.1"));
              portalclient.println(F("Host: forum.reefangel.com"));
              portalclient.println(F("Connection: close"));
              portalclient.println();
            }
            else
            {
              Serial.println(F("Failed to connect"));
            }
          }
          if (currentLine.startsWith("CLOUD:"))
          {
            //Serial.print(currentLine);
            if (currentLine.length() < 32)
            {
              if (CloudClient.connected())
              {
                //Serial.println(F("Publishing "));
                currentLine.replace("CLOUD:", "");
                Serial.print(currentLine);
                char pub_buffer[sizeof(CLOUD_USERNAME) + 5];
                sprintf(pub_buffer, "%s/out", CLOUD_USERNAME);
                char pub_msg[currentLine.length() + 1];
                currentLine.toCharArray(pub_msg, currentLine.length() + 1);
                CloudClient.publish(pub_buffer, pub_msg);
              }
            }
            else
            {
              Serial.print(F("Error on incoming data: "));
              Serial.println(currentLine.length());
            }
          }
          currentLine = "";
          digitalWrite(6, false);
        }
      }
    }
    digitalWrite(6, false);    
    while (portalclient.available()) {
      char c = portalclient.read();
      //      Serial.write(c);
    }
    if (PortalConnection && !portalclient.connected()) {
      PortalConnection = false;
      //      Serial.println();
      Serial.println(F("Disconnecting from Portal Server"));
      digitalWrite(6, false);
      portalclient.stop();
    }
  }

  CloudClient.loop();
  if (millis() - MQTTReconnectmillis > 5000)
  {
    if (!CloudClient.connected())
    {
      char sub_buffer[sizeof(CLOUD_USERNAME) + 6];
      MQTTReconnectmillis = millis();
      digitalWrite(6, true);
      Serial.println(F("MQTT Connecting..."));
      Serial.print(F("Attemp: "));
      Serial.println(++connection_counter);
      if (connection_counter>5)
      {
        Serial.println(F("Unable to connect. Rebooting..."));
        digitalWrite(5, true);
        digitalWrite(6, false);
        digitalWrite(7, false);        
        while(1);
      }
      sprintf(sub_buffer, "RA-%s", CLOUD_USERNAME);
      if (CloudClient.connect(sub_buffer, CLOUD_USERNAME, CLOUD_PASSWORD))
      {
        sprintf(sub_buffer, "%s/in/#", CLOUD_USERNAME);
        Serial.println(F("MQTT succeeded"));
        CloudClient.subscribe(sub_buffer);
      }
      else
      {
        MQTTReconnectmillis = millis();
        Serial.println(F("MQTT failed"));
        CloudClient.disconnect();
      }
      digitalWrite(6, false);
    }
  }
  if (analogRead(0) < 600 && power_status)
  {
    power_status = false;
  }
  if (analogRead(0) > 600 && !power_status)
  {
    power_status = true;
  }
}

void SendData()
{
  serial_timeout = millis();
  //  Serial.print(currentLine);
  client.print(currentLine);
  currentLine = "";
}

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

void mqttcallback(char* topic, byte* payload, unsigned int length)
{
  Serial1.print("cloud:");
  for (int a=0;a<length;a++)
  {
    Serial1.write(payload[a]);
    Serial.write(payload[a]);
  }
  Serial1.print(" ");
  Serial.println();
}

void wdt_initialize()
{
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(4);
  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(2) |
                      GCLK_GENCTRL_GENEN |
                      GCLK_GENCTRL_SRC_OSCULP32K |
                      GCLK_GENCTRL_DIVSEL;
  while (GCLK->STATUS.bit.SYNCBUSY);  // Syncronize write to GENCTRL reg.
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT |
                      GCLK_CLKCTRL_CLKEN |
                      GCLK_CLKCTRL_GEN_GCLK2;
}

void wdt_enable()
{
  WDT->CTRL.reg &= ~WDT_CTRL_ENABLE;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CTRL reg.
  WDT->CONFIG.reg = WDT_CONFIG_PER(0xA);  // 0xA = 8192 ms
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CONFIG reg.
  WDT->INTENCLR.reg |= WDT_INTENCLR_EW;
  WDT->CTRL.reg |= WDT_CTRL_ENABLE;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CTRL reg.
}

void wdt_disable()
{
  WDT->CTRL.reg &= ~WDT_CTRL_ENABLE;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CTRL reg.
}

void wdt_reset()
{
  WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CLEAR reg.
}

// RA_STRING1=U2FsdGVkX1+McuCAkIlSsRrljZrxqPemQeco0gw3Qh0=
// RA_STRING2=U2FsdGVkX182otKKK42E19GJNzbCmJB4k/bR5D12l+Q=
// RA_STRING3=Monroe
MisterTang
Posts: 58
Joined: Sat Jan 04, 2014 3:12 pm

Re: Cloud Wifi Attachment

Post by MisterTang »

Roberto,

This looks really cool - but I'm not seeing a UApp for Android in the store but do see one in the iTunes store. Is there a plan to officially support Android?
Image
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

The Uapp should be in the apple store, but it is an outdated version.
It was originally designed to be the Apple app and got migrated to a web based application since it is entirely created as a web application natively.
I can also post on the Android store too.
I'm just waiting to get all the bugs out before I released it on both stores. It's a pain to release to the stores :(
In the mean time, you can use the browser of your phone to make it work.
Roberto.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

I have loaded the code and it has been in a state of trying to connect for an hr. constant solid currently. It owuld blink occasionally when i first plugged it in.
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

Can you pull log again?
Roberto.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

Ok, I tried to pull a log and it would not show a COM port to allow me to select. I disconnected the device and replugged in with my computer and COM showed up, i think plugged into the reef angel and it is working now. I was trying to get back before you had read my inital response as a simple reset fixed it. I will keep monitoring it and report back.

Again Thank you for your promptness, we are soon to travel out of the country and not being able to see the tank was a bit worrying to me.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

So something interesting is going on. and it had to do with the sequence of how i plugged the unit it. when i plug the unit into the reef angel it blinks green and goes solid green with an occasional blink, then when i plug into it with the computer to run the serial monitor the computer prompts that the usb was not recognized and disables and re-enables the port and at that point the wifi unit turns blue immediately and stays running. I unplugged the computer and it stayed blue, then after a while (longer than i know as i was not paying attention) the light completely turned off. Then the cycle starts over.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

I re uploaded as well reset the whole system and it is functioning now
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

This is going on with both the old and the new routers. With the modified code below i am getting "Attempting to connect to Network named:
Not connected..." on the serial monitor scrolling over and over. It is staying constant green. With the webwizard code it is connecting to the wifi and upload but only when i have the usb cable plugged into the side and plugged into the computer. as soon as i unplug it changes to teal for a couple seconds and then goes back to solid green and an occansional blink. when i plug the usb back in it states the usb is not recognized and then refreshes the connection itself by diconnecting and reconnecting and at that point it turns blue and acts normal.

*Edit: something interesting i just tried. i plugged the usb cable into the side of the wifi unit and then into the iphone wall charger. it started working with that plugged in as well and the webwizard code.... not sure what to make of that.

Code: Select all

#include <SPI.h>
#include <WiFi101.h>
#include <PubSubClient.h>
#include <RA_CustomSettings.h>

//char ssid[32];
//char pass[32];
//char username[16];
//char password[16];

unsigned long client_timeout = millis();
unsigned long wifi_connection = millis();
unsigned long serial_timeout = millis();
unsigned long led_blink = millis();
unsigned long MQTTReconnectmillis = millis();

boolean LED_status = false;
boolean PortalConnection = false;
boolean power_status = true;
boolean data_ready = false;
int status = WL_IDLE_STATUS;
int connection_counter=0;
WiFiServer server(2000);
WiFiClient client;
WiFiClient portalclient;
WiFiClient mqttclient;

void mqttcallback(char* topic, byte* payload, unsigned int length);

PubSubClient CloudClient(MQTTServer, MQTTPORT, mqttcallback, mqttclient);

String currentLine = "";                // make a String to hold incoming data from the client

void setup() {
  Serial.begin(57600);      // initialize serial communication
  Serial1.begin(57600);      // initialize serial communication
  pinMode(5, OUTPUT);      // set the LED pin mode
  pinMode(6, OUTPUT);      // set the LED pin mode
  pinMode(7, OUTPUT);      // set the LED pin mode
  wdt_initialize();
  wdt_enable();
}

void loop() {
  wdt_reset();
  status = WiFi.status();
  while ( status != WL_CONNECTED) {
    digitalWrite(5, false);
    digitalWrite(6, true);
    digitalWrite(7, false);
    if (millis() - wifi_connection > 1000)
    {
      wifi_connection = millis();
      Serial.print("Attempting to connect to Network named: ");
      Serial.println(WIFI_SSID);                   // print the network name (SSID);
      if (WiFi.begin(WIFI_SSID, WIFI_PASS) == WL_CONNECTED)
      {
        status = WiFi.status();
        server.begin();                           // start the web server on port 2000
        printWifiStatus();                        // you're connected now, so print out the status
        digitalWrite(6, false);
        digitalWrite(7, true);
      }
      Serial.println("Not connected...");
    }
    wdt_reset();
  }

  client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println(F("new incoming client"));           // print a message out the serial port
    client_timeout = millis();
    while (client.connected()) {            // loop while the client's connected
      if (millis() - client_timeout > 2000) client.stop();
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        Serial1.write(c);                   // send to Reef Angel
      }
      if (Serial1.available())
      {
        char c = Serial1.read();
        //Serial.write(c);
        currentLine += c;
        serial_timeout = millis();
        if (currentLine.length() == 255) {
          SendData();
        }
      }
      if (millis() - serial_timeout > 400)
      {
        if (currentLine.length() > 0) {
          SendData();
          Serial.println();
          client.println();
        }
      }
      if (millis() - led_blink > 50)
      {
        led_blink = millis();
        LED_status = !LED_status;
        digitalWrite(6, LED_status);
      }
    }
    // close the connection:
    client.stop();
    Serial.println(F("incoming client disconnected"));
    digitalWrite(6, false);
  }
  else
  {
    while (Serial1.available())
    {
      char c = Serial1.read();
      currentLine += c;
      if(c==10) data_ready=true;
      serial_timeout = millis();
      digitalWrite(6, true);
      if (millis() - serial_timeout > 200 || data_ready)
      {
        if (currentLine.length() > 0) 
        {
          data_ready=false;
          serial_timeout = millis();
          //        Serial.print(currentLine);
          if (currentLine.startsWith("GET"))
          {
            PortalConnection = true;
            Serial.println(F("Connecting to Portal Server"));
            if (portalclient.connect(PortalServer, 80)) {
              Serial.println(F("Connected"));
              // Make a HTTP request:
              portalclient.print(currentLine);
              portalclient.println(F(" HTTP/1.1"));
              portalclient.println(F("Host: forum.reefangel.com"));
              portalclient.println(F("Connection: close"));
              portalclient.println();
            }
            else
            {
              Serial.println(F("Failed to connect"));
            }
          }
          if (currentLine.startsWith("CLOUD:"))
          {
            //Serial.print(currentLine);
            if (currentLine.length() < 32)
            {
              if (CloudClient.connected())
              {
                //Serial.println(F("Publishing "));
                currentLine.replace("CLOUD:", "");
                Serial.print(currentLine);
                char pub_buffer[sizeof(CLOUD_USERNAME) + 5];
                sprintf(pub_buffer, "%s/out", CLOUD_USERNAME);
                char pub_msg[currentLine.length() + 1];
                currentLine.toCharArray(pub_msg, currentLine.length() + 1);
                CloudClient.publish(pub_buffer, pub_msg);
              }
            }
            else
            {
              Serial.print(F("Error on incoming data: "));
              Serial.println(currentLine.length());
            }
          }
          currentLine = "";
          digitalWrite(6, false);
        }
      }
    }
    digitalWrite(6, false);    
    while (portalclient.available()) {
      char c = portalclient.read();
      //      Serial.write(c);
    }
    if (PortalConnection && !portalclient.connected()) {
      PortalConnection = false;
      //      Serial.println();
      Serial.println(F("Disconnecting from Portal Server"));
      digitalWrite(6, false);
      portalclient.stop();
    }
  }

  CloudClient.loop();
  if (millis() - MQTTReconnectmillis > 5000)
  {
    if (!CloudClient.connected())
    {
      char sub_buffer[sizeof(CLOUD_USERNAME) + 6];
      MQTTReconnectmillis = millis();
      digitalWrite(6, true);
      Serial.println(F("MQTT Connecting..."));
      Serial.print(F("Attemp: "));
      Serial.println(++connection_counter);
      if (connection_counter>5)
      {
        Serial.println(F("Unable to connect. Rebooting..."));
        digitalWrite(5, true);
        digitalWrite(6, false);
        digitalWrite(7, false);        
        while(1);
      }
      sprintf(sub_buffer, "RA-%s", CLOUD_USERNAME);
      if (CloudClient.connect(sub_buffer, CLOUD_USERNAME, CLOUD_PASSWORD))
      {
        sprintf(sub_buffer, "%s/in/#", CLOUD_USERNAME);
        Serial.println(F("MQTT succeeded"));
        CloudClient.subscribe(sub_buffer);
      }
      else
      {
        MQTTReconnectmillis = millis();
        Serial.println(F("MQTT failed"));
        CloudClient.disconnect();
      }
      digitalWrite(6, false);
    }
  }
  if (analogRead(0) < 600 && power_status)
  {
    power_status = false;
  }
  if (analogRead(0) > 600 && !power_status)
  {
    power_status = true;
  }
}

void SendData()
{
  serial_timeout = millis();
  //  Serial.print(currentLine);
  client.print(currentLine);
  currentLine = "";
}

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

void mqttcallback(char* topic, byte* payload, unsigned int length)
{
  Serial1.print("cloud:");
  for (int a=0;a<length;a++)
  {
    Serial1.write(payload[a]);
    Serial.write(payload[a]);
  }
  Serial1.print(" ");
  Serial.println();
}

void wdt_initialize()
{
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(4);
  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(2) |
                      GCLK_GENCTRL_GENEN |
                      GCLK_GENCTRL_SRC_OSCULP32K |
                      GCLK_GENCTRL_DIVSEL;
  while (GCLK->STATUS.bit.SYNCBUSY);  // Syncronize write to GENCTRL reg.
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT |
                      GCLK_CLKCTRL_CLKEN |
                      GCLK_CLKCTRL_GEN_GCLK2;
}

void wdt_enable()
{
  WDT->CTRL.reg &= ~WDT_CTRL_ENABLE;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CTRL reg.
  WDT->CONFIG.reg = WDT_CONFIG_PER(0xA);  // 0xA = 8192 ms
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CONFIG reg.
  WDT->INTENCLR.reg |= WDT_INTENCLR_EW;
  WDT->CTRL.reg |= WDT_CTRL_ENABLE;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CTRL reg.
}

void wdt_disable()
{
  WDT->CTRL.reg &= ~WDT_CTRL_ENABLE;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CTRL reg.
}

void wdt_reset()
{
  WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY;
  while (WDT->STATUS.bit.SYNCBUSY);  // Syncronize write to CLEAR reg.
}

// RA_STRING1=U2FsdGVkX1+AF2lnzKYQyNPLiQGZJdj0GFAKsjt2XXk=
// RA_STRING2=U2FsdGVkX1+K7xN24vw/vykDiKano9Ep9ArQL7gfcck=
// RA_STRING3=Monroe
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

Anyone?
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

You mentioned it was working.
Is it not?
Roberto.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

No it's doing as in my second to last post on page 4. It stays green until I plug in the USB into a computer or wall outlet. Then it acts as if it resets itself so I can't serial monitor it. I had left it connected to just the reef angel since the 8th and it stayed connected until the 11th and had been solid green since then as I am out of the country and have a house sitter telling me the color of the light.
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

If you are getting this: "Attempting to connect to Network named:
Not connected..."
Your code is wrong. It doesn't have the SSID name.
Roberto.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

It does connect and I have verified data going to the U-App. The problem is after a time it goes to green light only, It's acting as if it locks up. My previous posts explain the steps I've taken after it locks up.
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

How far is it from router?
I wonder if it is loosing the connection.
Roberto.
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

It is 10-15' and open air connection. No blockages. In the same room
slm222
Posts: 105
Joined: Wed Nov 18, 2015 9:16 pm

Re: Cloud Wifi Attachment

Post by slm222 »

I have been gone to Europe for 2 weeks and upon return it seems to unit is completely dead. Plugging in the USB or the cable to the reef angel. Should I email support?
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

Yes, send an email to info@reefangel.com
Roberto.
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Cloud Wifi Attachment

Post by rimai »

Kungpaoshizi wrote:Can we expect code uploads via the device someday?
I think that's one thing I would really like... Though I have gotten to the point where my reef tank setup is simple enough I don't need to change anything... lol
It seems jeelabs has released a couple months ago an alpha version of a firmware that emulates the stk500v2 that is required to program our microcontrollers :)
https://github.com/jeelabs/esp-link/releases
If someone is interested in investigating and checking if we could use it, let me know.
Roberto.
89delta
Posts: 163
Joined: Mon Oct 15, 2012 7:21 pm
Location: Leesburg, GA

Re: Cloud Wifi Attachment

Post by 89delta »

Am Getting an error trying to compile...I have the latest Dev library.

My_RA_Version_3_0.cpp: In function 'void loop()':
My_RA_Version_3_0:372: error: 'class ReefAngelClass' has no member named 'CloudPortal'

Code: Select all

//Sketch Date May 13, 2018
//Version 3.0

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <PAR.h>
#include <ReefAngel.h>
#include <SunLocation.h>
#define NUMBERS_8x16
#define PI 3.141593
//#include <RA_PCA9685.h>
//#define I2CPWM_PCA9685      0x40

////// Place global variable code below here


SunLocation sl;


byte ActinicPWMValue=0;
byte DaylightPWMValue=0;

////// Memory Locations used by all formulas
#define Mem_B_AcclDay  100 
// Note - 100 represents the hard coded Memory location used for this variable 
// not the value for the variable.
// This variable represents the current day in Acclimation Cycle. Set this value to the same 
// number of days as your AcclDuration to begin the acclimation cycle or set it to 0 to use 
// your normal dimming end% values.
#define Mem_B_AcclDuration  101 // Set this value to how many days you want your acclimation cycle to take.
#define Mem_B_SlopeStart  102 // Set this to your desired starting acclimation end%.

/////// Memory Locations used by Blue Formula
#define Mem_B_AcclStartEndBlue  105    // Acclimation starting Slope end%
#define Mem_B_SlopeEndBlue  106    // slope End%
////// Memory Locations used by White Formula
#define Mem_B_AcclStartEndWhite  110 // Acclimation starting Slope end%
#define Mem_B_SlopeEndWhite  111    // Slope End%


// Define Portal Variables
#define Var_AcclDay         0  // Days Left in Coral Acclimation
//#define Var_StormMode       1  // Lightning Storm Mode: 0=Slow (For Unresponsive LED Drivers: Meanwell ELN), 1=Fast, 2=Mega
//#define Var_Wmaker       2
//#define Var_Tide         3
//#define Var_TideMode     4
//#define Var_LogATO       5
//#define Var_WCVol        6
#define Var_weekday        7  //Day of the week: 0=Sunday, 1=Monday, 2=Tuesday, 3=Wednsday, 4=Thursday, 5=Friday, 6=Saturday



void DrawCustomMain() {  //5,14
  char buf[16];
  //byte acclDay = InternalMemory.read(Mem_B_AcclDay);
  
  ReefAngel.LCD.DrawLargeText(COLOR_BLACK, COLOR_WHITE, 10, 4, "Paradise Reef");
  ReefAngel.LCD.DrawDate(6, 118);
  
  // Temp and PH
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,10,65,"Temp:");
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,85, 65, "PH:");
  ConvertNumToString(buf, ReefAngel.Params.Temp[T2_PROBE], 10);
  ReefAngel.LCD.DrawText(T2TempColor, DefaultBGColor, 48, 73, buf); 
  ConvertNumToString(buf, ReefAngel.Params.Temp[T1_PROBE], 10);
  ReefAngel.LCD.DrawLargeText(T1TempColor, DefaultBGColor, 10, 75, buf, Num8x16);
  ConvertNumToString(buf, ReefAngel.Params.PH, 100);
  ReefAngel.LCD.DrawLargeText(PHColor, DefaultBGColor, 85, 75, buf, Num8x16);
  ConvertNumToString(buf, ReefAngel.Params.Temp[T3_PROBE], 10);
  ReefAngel.LCD.DrawText(T3TempColor, DefaultBGColor, 48, 82, buf);

  //ATO   
  /*ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,15,40,"High",Font8x16);
  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,85,40,"Low",Font8x16);  
  if (ReefAngel.HighATO.IsActive()) {
    ReefAngel.LCD.FillCircle(55,43,5,COLOR_GREEN);
  } else {
    ReefAngel.LCD.FillCircle(55,43,5,COLOR_RED);
  }  
  if (ReefAngel.LowATO.IsActive()) {
    ReefAngel.LCD.FillCircle(70,43,5,COLOR_GREEN);
  } else {
    ReefAngel.LCD.FillCircle(70,43,5,COLOR_RED);
  }
  */
  //Salinity
    char text[10];
  ConvertNumToString(text, ReefAngel.Params.Salinity, 10);
  strcat(text,"  ");
  ReefAngel.LCD.DrawText(DefaultFGColor,DefaultBGColor,15,93,"Salinity:");
  ReefAngel.LCD.DrawText(COLOR_NAVY,DefaultBGColor,75,93,text);
  pingSerial();
  
  //Sunrise/Sunset Times
  //ReefAngel.LCD.DrawText(0,255,5,52,"Rise:");
  //ReefAngel.LCD.DrawText(0,255,38,52,(sl.GetRiseHour()));
  //ReefAngel.LCD.DrawText(0,255,43,52,":");
  //ReefAngel.LCD.DrawText(0,255,48,52,(sl.GetRiseMinute()));
  //ReefAngel.LCD.DrawText(0,255,65,52,"Set:");
  //ReefAngel.LCD.DrawText(0,255,88,52,(sl.GetSetHour()));
  //ReefAngel.LCD.DrawText(0,255,100,52,":");
  //ReefAngel.LCD.DrawText(0,255,105,52,(sl.GetSetMinute()));
  
  //Moon Phase
  //ReefAngel.LCD.DrawText(0,255,5,72,"Moon:");
  //ReefAngel.LCD.DrawText(0,255,35,72,MoonPhaseLabel());
  
  // Display Acclimation timer
  //ReefAngel.LCD.DrawText(0,255,5,82,"Acclimation Day:");
  //ReefAngel.LCD.DrawText(0,255,105,82,acclDay);
  
  // Display LED %
  //ReefAngel.LCD.DrawText(0,255,12,92, "CW:");  
  //ReefAngel.LCD.DrawText(197,255,32,92, ReefAngel.PWM.GetDaylightValue()); 
  //ReefAngel.LCD.DrawText(0,255,72,92, "RB:");   
  //ReefAngel.LCD.DrawText(3,255,92,92, ReefAngel.PWM.GetActinicValue());    
  
  // Display Relays  
  byte TempRelay = ReefAngel.Relay.RelayData;
  TempRelay &= ReefAngel.Relay.RelayMaskOff;
  TempRelay |= ReefAngel.Relay.RelayMaskOn;
  ReefAngel.LCD.DrawOutletBox(12, 102, TempRelay);
  
  
  
  
}
void DrawCustomGraph()
{
    ReefAngel.LCD.DrawGraph(5, 12);
}

/*void SeasonalTemps ()
 {
  static int heatArray[][2] = { {792,796},// default in case of error in month=0 (June)
                    {770,774},//January (winter)
                    {774,778},//February (winter)
                    {775,779},//March (early spring)
                    {779,783},//April (spring)
                    {785,789},//May (spring)
                    {792,796},//June (early summer)
                    {799,803},//July (summer)
                    {806,810},//August (summer)
                    {796,800},//September (early fall)
                    {787,791},//October (fall)
                    {780,784},//November (fall)
                    {775,779} };//December (early winter)
                    
               
  ReefAngel.StandardHeater( Port2,heatArray[month()][0],heatArray[month()][1]);
*/
////// Place global variable code above here


void setup()
{
    // This must be the first line
    ReefAngel.Init();  //Initialize controller
    //ReefAngel.Use2014Screen();  // Let's use 2014 Screen

    ReefAngel.AddSalinityExpansion();

    // Default Salinity compensation
    ReefAngel.Salinity.SetCompensation(0);
    
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = Port3Bit| Port5Bit | Port6Bit ;
    
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port2Bit | Port3Bit | Port5Bit | Port6Bit;
    
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = Port7Bit | Port8Bit;
    
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port2Bit;
    
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    // Ports that are always on
    ReefAngel.Relay.On( Port1 );
    ReefAngel.Relay.On( Port2 );
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );

    ////// Place additional initialization code below here
    
  //sl.Init(-21.285833, 150.999722); // In decimal numbers (i.e. -18.285833, 147.699722)
  //sl.SetOffset(11,0,11,0); // rise_hour, rise_seconds, set_hour, set_seconds
    sl.Init(38.8447,-104.8433); // Colorado Springs,CO In decimal numbers (i.e. -18.285833, 147.699722)
    sl.SetOffset(-6,0,-6,0); // rise_hour, rise_seconds, set_hour, set_seconds
    
    
  randomSeed(now()/SECS_PER_DAY);
    
  //if (InternalMemory.read(Mem_B_ResetMemory)) 
    //init_memory();    
    ////// Place additional initialization code above here
}

void loop()
{
  
    
        //Temp Code for Fuge Light
    //ReefAngel.StandardLights( Port5,sl.GetSetHour(),sl.GetSetMinute(),sl.GetRiseHour(),sl.GetRiseMinute() );
    //ReefAngel.StandardLights( Port7,sl.GetSetHour(),sl.GetSetMinute(),sl.GetRiseHour(),sl.GetRiseMinute() );
    
    // 15min Delay on Skimmer coming from Feeding oe Waterchange Modes
    ReefAngel.Relay.DelayedOn(Port3, 10);
    
    // Custom Variable for Portal
    ReefAngel.CustomVar[Var_weekday]=weekday();
 
    //Heater   
    //SeasonalTemps();
    
    // Jebao Code   
    // Feeeding and Water Change mode speed
    ReefAngel.DCPump.FeedingSpeed=0;
    ReefAngel.DCPump.WaterChangeSpeed=0;
    pinMode(lowATOPin,OUTPUT);
    analogWrite(lowATOPin,ReefCrestMode(50,20,true)*2.55);
    ReefAngel.DCPump.UseMemory = true;
    ReefAngel.DCPump.LowATOChannel = AntiSync ; // Jebo connected to AtoPINLow
    pinMode(highATOPin,OUTPUT);
    analogWrite(highATOPin,ReefCrestMode(50,20,true)*2.55);
    ReefAngel.DCPump.UseMemory = true;
    ReefAngel.DCPump.HighATOChannel = AntiSync ; // Jebo connected to AtoPINLow
    

    // Add random mode if we set to Mode to Custom in portal
    static int rmode;
    static boolean changeMode=true;
    
    
    // These are the modes we can cycle through. You can add more and even repeat...
    byte modes[] = { Lagoon, ReefCrest, TidalSwell, Sine, Gyre, NutrientTransport, Else };
    
    if (now()%SECS_PER_DAY==0 || changeMode==true) { // Change at midnight or if controller rebooted
    rmode=random(100)%sizeof(modes); // Change the mode once per day to pick from our array
    changeMode=true;
    }
    
    // Set timer when in feeding mode
    static unsigned long feeding;
    if (ReefAngel.DisplayedMenu==FEEDING_MODE) feeding=now();
    
    if (now()-feeding<900) { 
      // First 15 minutes after feeding mode stops. Smart_NTM
      ReefAngel.DCPump.UseMemory=false;
      ReefAngel.DCPump.Mode=NutrientTransport;
    } else if (now()-feeding<2250) { // 15 minutes plus 1 hour
      // Continue NTM for the next 60 minutes (75 minutes total)
      ReefAngel.DCPump.UseMemory=false;
      ReefAngel.DCPump.Mode=NutrientTransport;
    } else if (now()%SECS_PER_DAY<23400 || now()%SECS_PER_DAY>=72000) { // 6:30am / 8:00pm
      // Night mode (go to 30%)
      ReefAngel.DCPump.UseMemory=false;
      ReefAngel.DCPump.Mode=Constant;
      ReefAngel.DCPump.Speed=32;
      //ReefAngel.DCPump.SetMode(Constant,31,45,30);
    } else if (InternalMemory.DCPumpMode_read()==11) { 
      // Custom Mode and nothing else going on
      ReefAngel.DCPump.UseMemory=false;
      ReefAngel.DCPump.Duration=InternalMemory.DCPumpDuration_read();
      if (modes[rmode]==Else) {
        ReefAngel.DCPump.UseMemory=false;
        ReefAngel.DCPump.Mode=Constant;
        ReefAngel.DCPump.Speed=ElseMode(InternalMemory.DCPumpSpeed_read(),25,true );    // ElseMode on sync mode, Portal Speed Setting +/- 25%
      } else {
        ReefAngel.DCPump.Mode=modes[rmode];  // Put the mode to the random mode :)
        ReefAngel.DCPump.Speed=InternalMemory.DCPumpSpeed_read(); // Set speed from portal
      }
    } else {
      ReefAngel.DCPump.UseMemory=true; // Will reset all values from memory
    }    
    ////// Place your custom code below here


////////// Variables used for all channels
  byte acclDay = InternalMemory.read(Mem_B_AcclDay); 
// What day in the acclimation cycle it is 
  byte acclDuration = InternalMemory.read(Mem_B_AcclDuration); 
// Acclimation Duration in days
  byte startPercent = InternalMemory.read(Mem_B_SlopeStart); 
// Normal start% being used by all 3 channels



////////// Blue Channel Variables
  byte acclStartEndBlue = InternalMemory.read(Mem_B_AcclStartEndBlue); 
// Starting End% for Acclimation cycle
  float acclendPercentBlue = InternalMemory.read(Mem_B_SlopeEndBlue); 
// Your target Blue end% once acclimation is complete

////////// Blue Channel Formula
  float acclPercentPerDayBlue = (acclendPercentBlue - acclStartEndBlue) / acclDuration;
// How much your Blue end% rises per acclimation day 
  float acclFactorBlue = acclDay * acclPercentPerDayBlue; 
// endPercentBlue will be offset by this much. If acclDay = 0 then this value will be 0
  byte endPercentBlue = acclendPercentBlue - acclFactorBlue; 
// Your final Blue end% for the day



////////// White Channel Variables
  byte acclStartEndWhite = InternalMemory.read(Mem_B_AcclStartEndWhite); 
// Starting End% for Acclimation cycle
  float acclEndPercentWhite = InternalMemory.read(Mem_B_SlopeEndWhite); 
// Your target White end% once acclimation is complete

////////// White Channel Formula
  float acclPercentPerDayWhite= (acclEndPercentWhite - acclStartEndWhite) / acclDuration; 
// How much your White end% rises per acclimation day
  float acclFactorWhite = acclDay * acclPercentPerDayWhite; 
// endPercentWhite will be offset by this much. If acclDay = 0 then this value will be 0
  byte endPercentWhite = acclEndPercentWhite - acclFactorWhite; 

// Custom Variable for Portal
ReefAngel.CustomVar[Var_AcclDay]=acclDay;

// At the end of the day, we need to decrement the acclimation counter.
  static boolean acclCounterReady=false; // We need a boolean so we only do this once per day
  if (now()%SECS_PER_DAY!=0) acclCounterReady=true; // If it's not midnight we'll get the boolean ready
  if (now()%SECS_PER_DAY==0 && acclCounterReady && acclDay>0) { // It's midnight, our bool is true and acclDay is more than 0
    acclDay--; // Reduce the counter
    acclCounterReady=false; // Reset the boolean flag
    InternalMemory.write(Mem_B_AcclDay,acclDay); // Update memory
}
    
    // handle updating sunrise and sunset values
    sl.CheckAndUpdate();

    ////// Place your custom code above here

    // This should always be the last line
    //ReefAngel.Portal( "89delta","collin1123" );
    //ReefAngel.DDNS( "bc29" ); // Your DDNS is 89delta-bc29.myreefangel.com
    ReefAngel.CloudPortal();
    ReefAngel.ShowInterface();
    
    
  // Calculate your regular sunrise/sunset PWM value
  ActinicPWMValue=PWMSlope(sl.GetRiseHour(),sl.GetRiseMinute(),sl.GetSetHour(),sl.GetSetMinute(),startPercent,endPercentBlue,120,(MoonPhase()/3));
  DaylightPWMValue=PWMSlope(sl.GetRiseHour(),sl.GetRiseMinute(),sl.GetSetHour(),sl.GetSetMinute(),startPercent,endPercentWhite,120,DaylightPWMValue);
  
  CheckCloud();
  
  ReefAngel.PWM.SetActinic(ActinicPWMValue);
  ReefAngel.PWM.SetDaylight(DaylightPWMValue);    
  
  

}
// RA_STRING1=U2FsdGVkX19/CQXYxFHutorfRImY8TNubevs+8+6Sjw=
// RA_STRING2=U2FsdGVkX1+7EBoxpKZdEtYXEBfHD+fIe5qrAeDKTa8=
// RA_STRING3=Gundel
// RA_LABEL LABEL_ACTINIC=Actinic
// RA_LABEL LABEL_DAYLIGHT=Daylight




//*********************************************************************************************************************************
// Random Cloud/Thunderstorm effects function
void CheckCloud()
{

  // ------------------------------------------------------------
  // Change the values below to customize your cloud/storm effect

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
#define Clouds_Every_X_Days 1 

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 50

  // Minimum number of minutes for cloud duration.  Don't use max duration of less than 6
#define Min_Cloud_Duration 6

  // Maximum number of minutes for the cloud duration. Don't use max duration of more than 255
#define Max_Cloud_Duration 15

  // Minimum number of clouds that can happen per day
#define Min_Clouds_per_Day 3

  // Maximum number of clouds that can happen per day
#define Max_Clouds_per_Day 10

  // Only start the cloud effect after this setting
  // In this example, start could after 11:30am
#define Start_Cloud_After NumMins(7,30)

  // Always end the cloud effect before this setting
  // In this example, end could before 8:00pm
#define End_Cloud_Before NumMins(20,00)

  // Percentage chance of a lightning happen for every cloud
  // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Change_per_Cloud 55

  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen resul could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.

    //#define printdebug // Uncomment this for debug print on Serial Monitor window
  #define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process. 

    
  // Add Random Lightning modes
#define Slow 0    // 5 seconds of slow lightning in the middle of a cloud for ELN style (slow response) drivers
#define Fast 1    // 5 seconds of fast lightning in the middle of a cloud for LDD style (fast response) drivers
#define Mega 2    // Lightning throughout the cloud, higher chance as it gets darker
#define Mega2 3   // Like Mega, but with more lightning
  // Set which modes you want to use
  // Example:  { Slow, Fast, Mega, Mega2 } to randomize all four modes.  
  // { Mega2 } for just Mega2.  { Mega, Mega, Fast} for Mega and Fast, with twice the chance of Mega.
byte LightningModes[] = { Fast, Mega, Mega2 };

    //ReefAngel.CustomVar[Var_StormMode]=lightningMode;
//#define Var_StormMode
  // Change the values above to customize your cloud/storm effect
  // ------------------------------------------------------------
  // Do not change anything below here

  static byte cloudchance=255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  static byte lightningchance=0;
  static byte cloudindex=0;
  static byte lightningstatus=0;
  static int LastNumMins=0;
  static byte lightningMode=0;
  static boolean chooseLightning=true;
  
  static time_t DelayCounter=millis();    // Variable for lightning timing.  
  static int DelayTime=random(1000);      // Variable for lightning timimg.
  
  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

#ifdef forcecloudcalculation
    if (cloudchance==255)
#else
    if (hour()==0 && minute()==0 && second()==1 && cloudchance==255) 
#endif
    {
      randomSeed(millis());    // Seed the random number generator
      //Pick a random number between 0 and 99
      cloudchance=random(100); 
      // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
      if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
      // Check if today is day for clouds. 
      if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0; 
      // If we have cloud today
      if (cloudchance)
      {
        // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
        numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
        // pick the time that the first cloud will start
        // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day. 
        cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        lightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (lightningchance>Lightning_Change_per_Cloud) lightningchance=0; 
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect

  
  if (cloudchance)
  {
    //is it time for cloud yet?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,DaylightPWMValue,0,180);
      if (chooseLightning) { lightningMode=LightningModes[random(100)%sizeof(LightningModes)]; chooseLightning=false; } 
        switch (lightningMode) {
        case Mega:
        // Lightning chance from beginning of cloud through the end.  Chance increases with darkness of cloud.
      if (lightningchance && random(ReversePWMSlope(cloudstart,cloudstart+cloudduration,100,0,180))<1 && (millis()-DelayCounter)>DelayTime)
      {
        int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4.  
        for (int i=0; i<a; i++)
        {
          analogWrite(daylightPWMPin,random(DaylightPWMValue*2.55,255));    // Flash on
          int randy=random(20,80);    // Random number for a delay
          if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
          delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
          analogWrite(daylightPWMPin,DaylightPWMValue*2.55);      // Flash off
          delay(random(30,50));                // Wait from 30 to 49 ms 
          wdt_reset();
        }
        DelayCounter=millis();    // If we just had a round of flashes, then lets put in a longer delay
        DelayTime=random(1000);   // of up to a second for dramatic effect before we do another round. 
      }
      break;
       case Mega2:
        // Higher lightning chance from beginning of cloud through the end.  Chance increases with darkness of cloud.
      if (lightningchance && random(ReversePWMSlope(cloudstart,cloudstart+cloudduration,100,0,180))<2 && (millis()-DelayCounter)>DelayTime)
      {
        int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4.  
        for (int i=0; i<a; i++)
        {
          analogWrite(daylightPWMPin,random(DaylightPWMValue*2.55,255));    // Flash on
          int randy=random(20,80);    // Random number for a delay
          if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
          delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
          analogWrite(daylightPWMPin,DaylightPWMValue*2.55);      // Flash off
          delay(random(30,50));                // Wait from 30 to 49 ms 
          wdt_reset();
        }
        DelayCounter=millis();    // If we just had a round of flashes, then lets put in a longer delay
        DelayTime=random(1000);   // of up to a second for dramatic effect before we do another round. 
      }
      break;
        case Fast:
        // 5 seconds of lightning in the middle of the cloud
      if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5 && (millis()-DelayCounter)>DelayTime)
      {
        int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4.  
        for (int i=0; i<a; i++)
        {
          analogWrite(daylightPWMPin,random(DaylightPWMValue*2.55,255));    // Flash on
          int randy=random(20,80);    // Random number for a delay
          if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
          delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
          analogWrite(daylightPWMPin,DaylightPWMValue*2.55);      // Flash off
          delay(random(30,50));                // Wait from 30 to 49 ms 
          wdt_reset();
        }
        DelayCounter=millis();    // If we just had a round of flashes, then lets put in a longer delay
        DelayTime=random(1000);   // of up to a second for dramatic effect before we do another round. 
      }
      break;
        case Slow:
        // Slow lightning for 5 seconds in the middle of the cloud.  Suitable for slower ELN style drivers
         if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5) 
      {
        if (random(100)<20) lightningstatus=1; 
        else lightningstatus=0;
        if (lightningstatus)
        {
          DaylightPWMValue=100; 
        }
        else 
        {
          DaylightPWMValue=0;
        }
        delay(1);
      }
        break;
        default:
        break;
      }
    } else {
      chooseLightning=true; // Reset the flag to choose a new lightning type
    }
    
    if (NumMins(hour(),minute())>(cloudstart+cloudduration))
    {
      cloudindex++;
      if (cloudindex < numclouds)
      {
        cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        lightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (lightningchance>Lightning_Change_per_Cloud) lightningchance=0;
      }
    }
  }
  
  if (LastNumMins!=NumMins(hour(),minute()))
  {
    LastNumMins=NumMins(hour(),minute());
    //ReefAngel.LCD.Clear(255,0,62,132,132);
  //ReefAngel.LCD.DrawText(0,255,5,62,"C");
  //ReefAngel.LCD.DrawText(0,255,11,62,"00:00");
  //ReefAngel.LCD.DrawText(0,255,45,62,"L");
  //ReefAngel.LCD.DrawText(0,255,51,62,"00:00");
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11; else x=17;
      //ReefAngel.LCD.DrawText(0,255,x,62,(cloudstart/60));
      if ((cloudstart%60)>=10) x=29; else x=35;
      //ReefAngel.LCD.DrawText(0,255,x,62,(cloudstart%60));
    }
    //ReefAngel.LCD.DrawText(0,255,90,62,cloudduration);
    if (lightningchance) 
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      //ReefAngel.LCD.DrawText(0,255,x,62,((cloudstart+(cloudduration/2))/60));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      //ReefAngel.LCD.DrawText(0,255,x,62,((cloudstart+(cloudduration/2))%60));
    }
  }   
}

byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
  long n=elapsedSecsToday(now());
  cstart*=60;
  cend*=60;
  if (n<cstart) return PWMStart;
  if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
  if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
  if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
  if (n>cend) return PWMStart;
}

Post Reply