이 소스코드는
[ 최소 비용의 오실로스코프 만들기 ] 에 포함된 소스코드이다. 상세 내용은 링크를 참조하자.
1. ./Web_Oscilloscope_32.ino
/* ------------------------------------------------------
Tiny + Cheap Oscilloscope
[[ AP MODE Info]] SSID : OSCIL_V06, PWD : 12345678
+-------- GPL License --------------------------------+
do not remove contact & url
contact : terminal0070@gmail.com
blog : https://andy-power.blogspot.com/
-------------------------------------------------------- */
#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>
#include <SPIFFS.h>
#include "OscilloscopeClass.h"
#define LED_PIN 2 // ESP32 mini
#define SSID "your router SSID" // AP 모드인 경우 사용하지 않음
#define PWD "your router PWD" // AP 모드인 경우 사용하지 않음
#define AP_MODE true // AP 모드인지 아닌지..
// Station 모드인 경우 false로 하면 된다.
//--------------------------------------------------------------
// Global variables
//--------------------------------------------------------------
uint32_t g_dPrevMainInfoTime = 0; // 화면 정보를 마지막으로 전달한 시각
uint32_t g_dPrevSubInfoTime = 0; // 부가 정보를 마지막으로 전달한 시각
uint8_t g_dClientCnt = 0; // 연결된 클라이언트 수
WebServer g_WebServer(80); // WebServer
WebSocketsServer g_WebSocket(8080); // Websocket 서버
OscilloscopeClass oscilloscope; // 오실로스코프
//--------------------------------------------------------------
// Setup function
// start wifi + webserver + websocket server + oscilloscope
//--------------------------------------------------------------
void setup() {
Serial.begin(115200);
if (!SPIFFS.begin()) {
Serial.println("SPIFFS Mount Failed");
return;
}
pinMode(LED_PIN, OUTPUT);
wifiProcess(); // start wifi
g_WebSocket.begin();
g_WebSocket.onEvent(webSocketEvent);
g_WebServer.begin();
g_WebServer.on("/", handleWebRoot);
oscilloscope.begin();
}
//--------------------------------------------------------------
// Setup function
// start wifi + webserver + websocket server + oscilloscope
//--------------------------------------------------------------
void loop() {
if (AP_MODE == false) {
wifiProcess();
}
g_WebSocket.loop();
g_WebServer.handleClient();
if (oscilloscope.loop()) {
return;
}
uint32_t now = millis();
if (now - g_dPrevMainInfoTime < 100) {
if (now - g_dPrevSubInfoTime > 520) {
// send sub data ( 2 frames per 1s )
g_WebSocket.broadcastBIN(oscilloscope.getSubInfo(), oscilloscope.getSubInfoSize());
g_dPrevSubInfoTime = now;
} else {
delay(2);
}
return;
}
// send main data ( 10 frames per 1s)
g_WebSocket.broadcastBIN(oscilloscope.getScreenData(), oscilloscope.getScreenDataSize());
g_dPrevMainInfoTime = now;
return;
}
//--------------------------------------------------------------
// build wifi connection
//--------------------------------------------------------------
void wifiProcess() {
if (AP_MODE) {
// start soft ap
WiFi.mode(WIFI_AP_STA);
WiFi.softAP("OSCIL_V06", "12345678");
} else {
// 무선 공유기에 붙여서 쓰는 경우
if (WiFi.status() != WL_CONNECTED) {
bool ledOn = false;
WiFi.begin(SSID, PWD);
Serial.println("Connecting to WiFi..");
while (WiFi.status() != WL_CONNECTED) {
digitalWrite(LED_PIN, ledOn);
ledOn = !ledOn;
delay(200);
}
Serial.println(WiFi.localIP());
}
}
}
//--------------------------------------------------------------
// send html
//--------------------------------------------------------------
void handleWebRoot() {
File file = SPIFFS.open("/web_interface.html", "r");
String contents = file.readStringUntil(NULL);
if (!AP_MODE) {
contents.replace("192.168.4.1", WiFi.localIP().toString());
}
g_WebServer.send( 200, "text/html", contents );
}
//--------------------------------------------------------------
// process websocket event
//--------------------------------------------------------------
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
switch (type) {
case WStype_CONNECTED:
g_dClientCnt++;
if (g_dClientCnt > 0) { // 연결된 클라이언트가 있으면 LED 켜기
digitalWrite(LED_PIN, HIGH);
}
break;
case WStype_DISCONNECTED:
g_dClientCnt--;
if (g_dClientCnt <= 0) { // 연결된 클라이언트가 없으면 LED 끄기
digitalWrite(LED_PIN, LOW);
}
break;
case WStype_BIN: // 모든 데이터는 바이너리로 온다.
if (length >= 2) {
oscilloscope.processUI(payload);
g_dPrevSubInfoTime = 0;
}
break;
case WStype_TEXT: break;
case WStype_ERROR:
case WStype_FRAGMENT_TEXT_START:
case WStype_FRAGMENT_BIN_START:
case WStype_FRAGMENT:
case WStype_FRAGMENT_FIN:
break;
}
}
2. ./Oscilloscope.h
/* ------------------------------------------------------
Tiny + Cheap Oscilloscope
+-------- GPL License --------------------------------+
do not remove contact & url
contact : terminal0070@gmail.com
blog : https://andy-power.blogspot.com/
-------------------------------------------------------- */
#ifndef _OSCILLOSCOPE_CLASS_H_
#define _OSCILLOSCOPE_CLASS_H_
#include <Arduino.h>
#define ANALOG_PIN0 34 // 아날로그 입력 첫번째 핀.
#define ANALOG_PIN1 33
#define ANALOG_PIN2 35
#define CHART_WIDTH 300 // 차트 부분만 해당하는 가로 크기
// 이 숫자는 화면에 한번에 표시하는 너비이며,
// 웹방식에서는 통신을 고려해야 하기 때문에 큰 숫자는 그닥이다.
#define CHART_HEIGHT 190 // 차트 부분만 해당하는 세로 크기
#define MAX_READ_NUM (CHART_WIDTH + 80) // 최대 데이터 읽기..화면에 표시하는 범위보다 살짝 넓게..
#define SECTOR_WIDTH (MAX_READ_NUM * 8) // maximum of each channel
#define CHANNEL_NUM 3 // 현재는 3개 채널만 읽는다..(4개가 필요할까?)
class OscilloscopeClass {
public:
OscilloscopeClass();
void begin(void);
uint16_t getScreenDataSize(void);
uint8_t *getScreenData(void);
uint16_t getSubInfoSize(void);
uint8_t *getSubInfo(void);
void processUI(uint8_t* payload);
float getVolt(uint16_t analogPinValue);
bool loop(void);
private:
// 현재 상황에서 ADC를 통하여 읽어야 하는 데이터 개수..
// Tracking mode인 경우 평소의 3배까지 읽는다.
uint32_t m_dNowReadNum;
// ADC로 부터 데이터를 읽어서 저장하는 부분
// 일반적으로는 MAX_READ_NUM * 2 크기만큼 읽지만, Tracking모드인 경우 네배까지 읽음.
uint8_t* m_pOriginDatas; // 측정한 값들을 저장해 두는 곳
uint8_t* m_pScreenDatas; // 화면출력용으로 정제된 데이터를 저장하는 곳
uint8_t m_arySubInfos[40]; // 클라이언트에게 부가정보 보내기용 버퍼
uint16_t m_aryMaxValues[CHANNEL_NUM]; // 각 채널별 측정된 최고값
uint8_t m_dChNum; // 현재 몇개의 채널이 켜져 있는가?
bool m_bCollapseChannel; // 모아 보기 인지, 나누어 보기 인지 ..
uint8_t m_dTracking; // Tracking 모드인가 아닌가..
bool m_bWaitHigh; // Tracking 모드에서 기다리는 신호가 무엇인지(0에서 신호가 올라가는 것을 대기?)
uint16_t m_dScrollStart; // 측정된 데이터중에서 화면에 표시하는 시작 위치 (스크롤 위치)
// 정밀도.. 1이 가장 좋은 것이고.. 커질수록 나빠진다...
// 즉 m_dScale 이 3인 경우 ADC로 부터 세번 읽어서 두개는 버리고 하나의 값만 쓴다.
uint8_t m_dScale;
// 이건 한번 측정한 값을 얼마나..더 넓게 표시하냐의 단위이다
// 이 값이 3인 경우, 1번 측정한 값을 화면에 3개의 픽셀로 표시한다.
uint8_t m_dSubScale;
uint16_t m_dFrequency; // 채널1의 주파수..
void readADC(); // 아날로그 핀들을 이용하여 데이터를 읽는다.
void makeDisplayingtDatas(); // 화면 표시에 필요한 정보를 만든다.
void enterTrackingMode(); // Tracking 모드로 진입한다.
void trackingModeProcess(); // Tracking 모드에서 위상 변화를 대기중일때 처리함수
void leaveTrackingMode(); // Tracking 모드를 종료한다.
};
#endif //_OSCILLOSCOPE_CLASS_H_
3. ./Oscilloscope.cpp
/* ------------------------------------------------------
Tiny + Cheap Oscilloscope
+-------- GPL License --------------------------------+
do not remove contact & url
contact : terminal0070@gmail.com
blog : https://andy-power.blogspot.com/
-------------------------------------------------------- */
#include "OscilloscopeClass.h"
#define DEFAULT_MAX 7
#define min(a,b) (a > b ? b : a)
#define max(a,b) (a > b ? a : b)
OscilloscopeClass::OscilloscopeClass() {
m_dChNum = 3;
m_bCollapseChannel = false;
m_dTracking = 0; // 이 값은 3종류 이며 0이면 노말모드, 1이면 트래킹 대기 모드, 2이면 트래킹정보 표시 모드
m_dScale = 1; // 화면 축소비율
m_dSubScale = 1; // 화면 확대 비율
m_dScrollStart = 0;
m_dFrequency = 200; // 패널 1 기준 주파수..
m_dNowReadNum = MAX_READ_NUM * 2;
}
// 반드시 초기에 한번 호출되어야 한다.
void OscilloscopeClass::begin() {
m_pOriginDatas = (uint8_t*)malloc(SECTOR_WIDTH * CHANNEL_NUM); // almost 10K
m_pScreenDatas = (uint8_t*)malloc(CHART_WIDTH * CHANNEL_NUM + 2);
// 측정용 핀 정의
pinMode(ANALOG_PIN0, INPUT);
pinMode(ANALOG_PIN1, INPUT);
pinMode(ANALOG_PIN2, INPUT);
// ESP32의 측정 데시벨 정의
analogSetAttenuation(ADC_6db); // 2.2v 이상이면 최대값 // 11db 로 하면 노이즈가 생긴다.
// 10자리 수로 읽는다. (16비트까지 가능할것 같은데.. 의미가 있는지 모르겟슴)
analogReadResolution(10); // 최대 값이 1024로 설정 비트수가 너무 높으면. 노이즈가 생긴다.
delay(10);
}
// 최대 3개의 ADC 핀으로 부터 정보를 읽는다.
// 읽은 것은 m_pOriginDatas에 저장한다.
void OscilloscopeClass::readADC() {
int i, j;
if (m_dTracking == 0) { // Tracking 모드가 아닌경우
int check = 0;
uint32_t tstart;
// 파형의 첫번째 올라 가는 부분 찾기...
while (check++ < 2000) {
if (analogRead(ANALOG_PIN0) < 3 ) {
check = 0;
while (check++ < 2000) {
if (analogRead(ANALOG_PIN0) > 6) {
tstart = micros();
break;
}
}
break;
}
}
// 파형의 두번째 올라 가는 부분 찾기...
check = 0;
while (check++ < 2000) {
if (analogRead(ANALOG_PIN0) < 3 ) {
check = 0;
while (check++ < 2000) {
if (analogRead(ANALOG_PIN0) > 6) {
break;
}
}
break;
}
}
// find frequency
uint32_t tend = micros();
double pulseTime = (double)(tend - tstart);
pulseTime = max(pulseTime, 1);
m_dFrequency = 1001000 / pulseTime; //약간의 보정을 위해서 100.01%의 값으로 계산
m_dFrequency = max(m_dFrequency, 10);
}
// 정해진 숫자만큼 데이터를 읽는다.
for (i = 0; i < m_dNowReadNum; i ++) {
for (j = 0; j < m_dScale; j++) { // 이건 스킵의 개념이다.
m_pOriginDatas[SECTOR_WIDTH * 0 + i] = (uint8_t)(analogRead(ANALOG_PIN0) >> 2);
if (m_dChNum >= 2) {
m_pOriginDatas[SECTOR_WIDTH * 1 + i] = (uint8_t)(analogRead(ANALOG_PIN1) >> 2);
}
if (m_dChNum >= 3) {
m_pOriginDatas[SECTOR_WIDTH * 2 + i] = (uint8_t)(analogRead(ANALOG_PIN2) >> 2);
}
}
}
// 각 채널별로 최대값을 찾는다.
m_aryMaxValues[0] = DEFAULT_MAX; // 기본 최대값을 설정.
m_aryMaxValues[1] = DEFAULT_MAX; // 어느 정도 레벨 이하는 바닥에 깔리게 만들기 위함임
m_aryMaxValues[2] = DEFAULT_MAX; // 측정하는 것을 고려하여 변경해야 할수도 있음..
for (uint16_t i = 0; i < m_dNowReadNum; i ++) {
m_aryMaxValues[0] = max(m_aryMaxValues[0], m_pOriginDatas[i + SECTOR_WIDTH * 0]);
m_aryMaxValues[1] = max(m_aryMaxValues[1], m_pOriginDatas[i + SECTOR_WIDTH * 1]);
m_aryMaxValues[2] = max(m_aryMaxValues[2], m_pOriginDatas[i + SECTOR_WIDTH * 2]);
}
}
// 이미 읽어 놓은 ADC 값들을 화면 표시용 버퍼에 계산해서 넣는다.
// 클라이언트에 전달할때는 최대값을 256으로 맞추어 전달한다.
void OscilloscopeClass::makeDisplayingtDatas() {
uint16_t i, j, k;
float aryRatio[3];
if (m_bCollapseChannel) {
int maxpos = max(m_aryMaxValues[0], m_aryMaxValues[1]);
maxpos = max(maxpos, m_aryMaxValues[2]);
aryRatio[0] = 250 / (float)maxpos;
aryRatio[1] = 250 / (float)maxpos;
aryRatio[2] = 250 / (float)maxpos;
} else {
aryRatio[0] = 250 / (float)m_aryMaxValues[0];
aryRatio[1] = 250 / (float)m_aryMaxValues[1];
aryRatio[2] = 250 / (float)m_aryMaxValues[2];
}
// 측정값들이 미세한 경우 상하폭을 확대하지 않음.
for (i = 0; i < 3; i ++) {
if (m_aryMaxValues[i] < DEFAULT_MAX) aryRatio[i] = 1.0;
}
m_pScreenDatas[0] = 0; // main data flag
m_pScreenDatas[1] = m_dChNum; // 2nd byte is #channel
uint8_t * tmpPointer = m_pScreenDatas + 2;
int dataIndex = m_dScrollStart;
for (i = 0; i < CHART_WIDTH;) {
for (j = 0; j < m_dSubScale; j++) {
tmpPointer[i + CHART_WIDTH * 0] = (uint8_t)(aryRatio[0] * m_pOriginDatas[dataIndex + SECTOR_WIDTH * 0]);
tmpPointer[i + CHART_WIDTH * 1] = (uint8_t)(aryRatio[1] * m_pOriginDatas[dataIndex + SECTOR_WIDTH * 1]);
tmpPointer[i + CHART_WIDTH * 2] = (uint8_t)(aryRatio[2] * m_pOriginDatas[dataIndex + SECTOR_WIDTH * 2]);
i++;
if (i >= CHART_WIDTH) break;
}
dataIndex++;
}
}
//----------------------------------------------------------------------
// 전압 계산
// 330R 저항 하나, 100R 저항을 연결하여 분압한 경우이다.
//----------------------------------------------------------------------
// signal ----- 330R ---+---- 100R ---------- GND
// |
// ADC
//----------------------------------------------------------------------
// 여러 차수 방정식을 고려해 보았는데 1차 방정식이 가장 무난했다.
float OscilloscopeClass::getVolt(uint16_t analogPinValue) {
if (DEFAULT_MAX >= analogPinValue) { // 너무 적은 값이 측정되면 전압을 0v로 표시한다.
return 0.000001;
} else {
return (0.0289 * analogPinValue + 0.592);
}
}
//----------------------------------------------------------------------
// 중요 모드인 트래킹 모드 진입함수 이다.
// 트래킹 모드는 대기 모드후에 위상변화가 발생하면
// 그때부터 MAX_READ_NUM * 4 만큼 측정하여 화면에 표시한다.
// 트래킹 모드를 종료하기 전까지는 이미 측정된 내용만을 사용한다.
//----------------------------------------------------------------------
void OscilloscopeClass::enterTrackingMode() {
m_dTracking = 1;
m_dScrollStart = 0;
m_dSubScale = 1;
m_dScale = 1;
m_dNowReadNum = MAX_READ_NUM * 4;
// 일단 현재의 채널 값으로 차트를 평행선만 들어가게 만들고.
uint8_t ch0 = analogRead(ANALOG_PIN0) >> 2;
uint8_t ch1 = analogRead(ANALOG_PIN1) >> 2;
uint8_t ch2 = analogRead(ANALOG_PIN2) >> 2;
for (int i = 0; i < m_dNowReadNum; i ++) {
m_pOriginDatas[i + SECTOR_WIDTH * 0] = ch0;
m_pOriginDatas[i + SECTOR_WIDTH * 1] = ch1;
m_pOriginDatas[i + SECTOR_WIDTH * 2] = ch2;
}
// 위상 추적을 위에서 아래로 내려올때 할것인가,
// 아래에서 위로 올라갈때 할것인가를 정하는 부분
uint32_t loopCnt = 0;
uint16_t chk = analogRead(ANALOG_PIN0);
for (int i = 0; i < 100; i ++) {
chk = max(chk, analogRead(ANALOG_PIN0));
}
m_bWaitHigh = true;
// 즉 100번 측정한 최대값이 일정값 이상이면,
// 지금부터 내려가는 시점을 찾게 된다.
if (chk > DEFAULT_MAX) {
m_bWaitHigh = false;
}
}
// 일단 트래킹 모드가 시작되면 채널1 기준 위상 변화가 생기지 않으면
// 그대로 멈춰 있는다. 위상 변화가 감지되면 그때부터 MAX_READ_NUM * 4 회를 측정하고
// 화면에 표시한다.
void OscilloscopeClass::trackingModeProcess() {
uint16_t loopCnt = 0;
uint16_t chk;
// 위상이 변할때 까지 기다린다.
// 다만 어느정도 기다리면 그외에 ESP가 해야 할 일이 있을지도
// 모르기 때문에 잠시 다른 작업을 하기 위하여
// 함수를 빠져 나간다.
while (loopCnt < 10000) {
chk = analogRead(ANALOG_PIN0);
if (m_bWaitHigh && chk > DEFAULT_MAX) {
break;
} else if (!m_bWaitHigh && chk < 2) {
break;
}
loopCnt++;
}
if (loopCnt >= 10000) {
return;
}
// 위상 변화가 감지 되었으면 그때부터 MAX_READ_NUM * 4 회를 읽고 화면에 표시한다.
readADC();
makeDisplayingtDatas();
m_dTracking = 2; // 이 값은 3종류 이며 0이면 노말모드, 1이면 트래킹 대기 모드, 2이면 트래킹정보 표시 모드
}
// 트래킹된 정보를 표시하다가
// 사용자가 버튼을 눌러서 노말 모드로 돌아가는 경우
void OscilloscopeClass::leaveTrackingMode() {
m_dTracking = 0;
m_dNowReadNum = MAX_READ_NUM * 2;
m_dScrollStart = 0;
m_dSubScale = 1;
m_dScale = 1;
}
// 화면 표시 데이터 크기
uint16_t OscilloscopeClass::getScreenDataSize(void) {
return 2 + CHART_WIDTH * m_dChNum;
}
// 화면 표시용 데이터를 반환한다.
uint8_t *OscilloscopeClass::getScreenData(void) {
if (m_dTracking == 0) {
readADC();
makeDisplayingtDatas();
} else if (m_dTracking == 2) {
makeDisplayingtDatas();
}
return m_pScreenDatas;
}
// 메인데이터 이외에 부가 데이터의 크기
uint16_t OscilloscopeClass::getSubInfoSize(void) {
return 1 + 16; // heaer + data
}
//----------------------------------------------------------
// 부가 데이터 순서 ( sub info bytes order)
// 0 1 2 3 4 5
// code, collapsed flag, sub scale, scale, freq / 256, freq % 256,
// 6, 8, 10 7,9, 11
// volt (Integer part), volt (Fraction part * 10)
// 12 13
// #total_data / 256, #total_data % 256,
// 14 15
// m_dScrollStart / 256, m_dScrollStart % 256,
// 16
// m_dTracking
//----------------------------------------------------------
uint8_t *OscilloscopeClass::getSubInfo(void) {
m_arySubInfos[0] = 1; // main data flag
m_arySubInfos[1] = (m_bCollapseChannel ? 1 : 0);
m_arySubInfos[2] = m_dSubScale;
m_arySubInfos[3] = m_dScale;
m_arySubInfos[4] = (uint8_t)(m_dFrequency >> 8);
m_arySubInfos[5] = (uint8_t)(m_dFrequency & 0xff);
float volt = getVolt(m_aryMaxValues[0]);
m_arySubInfos[6] = (uint8_t)volt;
m_arySubInfos[7] = (uint8_t)((volt - (int)volt) * 10);
volt = getVolt(m_aryMaxValues[1]);
m_arySubInfos[8] = (uint8_t)volt;
m_arySubInfos[9] = (uint8_t)((volt - (int)volt) * 10);
volt = getVolt(m_aryMaxValues[2]);
m_arySubInfos[10] = (uint8_t)volt;
m_arySubInfos[11] = (uint8_t)((volt - (int)volt) * 10);
m_arySubInfos[12] = (uint8_t)(m_dNowReadNum >> 8);
m_arySubInfos[13] = (uint8_t)(m_dNowReadNum & 0xff);
m_arySubInfos[14] = (uint8_t)(m_dScrollStart >> 8);
m_arySubInfos[15] = (uint8_t)(m_dScrollStart & 0xff);
m_arySubInfos[16] = (uint8_t)(m_dTracking);
return m_arySubInfos;
}
//---------------------------------------------------
// 사용자 입력 처리
void OscilloscopeClass::processUI(uint8_t* payload) {
if (m_dTracking == 1) { // 위상 변화 대기중인 경우 무엇을 누르던 간에.. 그걸 우선 멈춘다...
readADC();
makeDisplayingtDatas();
m_dTracking = 2;
return;
} else if (payload[0] == 1) { // button ui processing
switch (payload[1]) {
case 0:
switch (m_dTracking) {
case 0:
m_dTracking = 1;
enterTrackingMode();
break;
case 1: // 이 함수의 첫부분에서 처리했슴
// canceled by user
break;
case 2:
leaveTrackingMode();
break;
}
break;
case 1: // zoom in
if (m_dScale > 1) {
m_dScale--;
} else {
m_dSubScale ++;
m_dSubScale = min(8, m_dSubScale);
}
break;
case 2: // zoom out
if (m_dSubScale > 1) {
m_dSubScale--;
} else {
m_dScale++;
m_dScale = min(8, m_dScale);
if (m_dTracking != 0) {
m_dScale = min(1, m_dScale);
}
}
break;
case 3: // < scroll left
if (m_dTracking == 0) {
m_dScrollStart = max(m_dScrollStart - 6, 0);
} else {
m_dScrollStart = max(m_dScrollStart - 18, 0);
}
break;
case 4: // > scroll right
if (m_dTracking == 0) {
m_dScrollStart = min(m_dScrollStart + 6, m_dNowReadNum - CHART_WIDTH - 1);
} else {
m_dScrollStart = min(m_dScrollStart + 18, m_dNowReadNum - CHART_WIDTH - 1);
}
break;
case 5: // collapse
m_bCollapseChannel = !m_bCollapseChannel;
break;
case 6: // change number of channel
m_dChNum++;
if (m_dChNum >= 4) m_dChNum = 1;
// Serial.println(m_dChNum);
break;
case 10: // scroll position
m_dScrollStart = payload[2] * 256 + payload[3];
m_dScrollStart = max(m_dScrollStart, 0);
m_dScrollStart = min(m_dScrollStart, m_dNowReadNum - CHART_WIDTH - 1);
}
}
}
// 정보를 클라이언트에게 보내야 하는 경우에는 false를 반환
bool OscilloscopeClass::loop(void) {
if (m_dTracking == 1) {
trackingModeProcess();
return true;
} else {
return false;
}
}