├── .gitignore ├── CircularBuffer.h ├── Config.cpp ├── Config.h ├── IO.ino ├── LDuino.ino ├── LDuino.sln ├── LDuino.vcxproj ├── Modbus.cpp ├── Modbus.h ├── ModbusIP.cpp ├── ModbusIP.h ├── ModbusRelay.cpp ├── ModbusRelay.h ├── ModbusSerial.cpp ├── ModbusSerial.h ├── README.md ├── TinyWebServer.cpp ├── TinyWebServer.h ├── __vm └── .LDuino.vsarduino.h ├── doc ├── LDmicro.png └── LDuino_web.png ├── httpd-fsdata.h ├── keywords.txt ├── ldmicro ├── LDuino-valid.ld ├── LDuino-valid.pl ├── LDuino-valid.xint ├── LDuino-validv2.ld ├── LDuino-validv2.pl └── LDuino-validv2.xint ├── lduino_engine.cpp ├── lduino_engine.h ├── malloc.c ├── mkfs.bat ├── plcweb.cpp ├── plcweb.h ├── sectionname.h ├── stdlib_private.h ├── sysinfo.cpp ├── sysinfo.h ├── tools ├── makefsdata.pl └── makefsdata.py ├── web ├── animated_status.css ├── config.html ├── config.js ├── getconfig.xml ├── getstate.xml ├── ignore.txt ├── index.html ├── lduino.js ├── led.png ├── range.css ├── styles.css ├── switch.css ├── zepto.min.js └── zepto_extras.js ├── xmlstring.cpp └── xmlstring.h /.gitignore: -------------------------------------------------------------------------------- 1 | /x64/Release 2 | /webbackup.zip 3 | /webbackup/web 4 | /Debug 5 | /Release 6 | /__vm 7 | /__vm/Upload.vmps.xml 8 | /__vm/Configuration.Release.vmps.xml 9 | /__vm/Configuration.Debug.vmps.xml 10 | /__vm/Compile.vmps_ASUS-FREDO_nov.-13-144935-2016_Conflict.xml 11 | /__vm/Compile.vmps_ASUS-FREDO_nov.-13-144926-2016_Conflict.xml 12 | /__vm/Compile.vmps.xml 13 | /LDuino.vcxproj.user 14 | /LDuino.filters 15 | /.vs/config/applicationhost.config 16 | /ldmicro/ldmicro.tmp 17 | /ldmicro/toto.ld 18 | *.bak 19 | *.org 20 | /~AutoRecover.LDuino.vcxproj 21 | /.vs/emcoturn_120_V01/v14/.suo 22 | /.vs/LDuino/v14/.suo 23 | /web/Thumbs.db 24 | -------------------------------------------------------------------------------- /CircularBuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | CircularBuffer.h - circular buffer library for Arduino. 3 | 4 | Copyright (c) 2009 Hiroki Yagita. 5 | 6 | Modified by Frederic Rible for pattern matching (2016-04-22) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | 'Software'), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | #ifndef CIRCULARBUFFER_h 28 | #define CIRCULARBUFFER_h 29 | #include 30 | 31 | template 32 | class CircularBuffer { 33 | public: 34 | enum { 35 | Empty = 0, 36 | Half = Size / 2, 37 | Full = Size, 38 | }; 39 | 40 | CircularBuffer() : 41 | wp_(buf_), rp_(buf_), tail_(buf_+Size), remain_(0) {} 42 | ~CircularBuffer() {} 43 | 44 | void push(T value) { 45 | if (remain_ == Size) pop(); // We full, drop one element 46 | *wp_++ = value; 47 | remain_++; 48 | if (wp_ == tail_) wp_ = buf_; 49 | } 50 | 51 | T pop() { 52 | T result = *rp_++; 53 | remain_--; 54 | if (rp_ == tail_) rp_ = buf_; 55 | return result; 56 | } 57 | 58 | boolean match(T *buf, int len) { 59 | if (len > remain_) return false; 60 | T *p = rp_; 61 | for (int i = 0; i < len; i++) { 62 | if (*p++ != *buf++) return false; 63 | if (p == tail_) p = buf_; 64 | } 65 | return true; 66 | } 67 | 68 | int remain() const { 69 | return remain_; 70 | } 71 | 72 | void flush() { 73 | wp_ = buf_; 74 | rp_ = buf_; 75 | tail_ = buf_ + Size; 76 | remain_ = 0; 77 | } 78 | private: 79 | T buf_[Size]; 80 | T *wp_; 81 | T *rp_; 82 | T *tail_; 83 | uint16_t remain_; 84 | }; 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /Config.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Frederic Rible 2 | // 3 | // This file is part of LDuino, an Arduino based PLC software compatible with LDmicro. 4 | // 5 | // LDuino is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // LDuino is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with LDmicro. If not, see . 17 | 18 | #include > 19 | #include "Config.h" 20 | #include "xmlstring.h" 21 | 22 | // Code coming from http://www.ccontrolsys.com/w/How_to_Compute_the_Modbus_RTU_Message_CRC 23 | 24 | uint16_t Config::eeprom_crc(int minus) 25 | { 26 | uint16_t crc = ~0L; 27 | 28 | for (int index = 0; index < EEPROM.length() - minus; ++index) { 29 | crc ^= EEPROM[index]; // XOR byte into least sig. byte of crc 30 | 31 | for (int i = 8; i != 0; i--) { // Loop over each bit 32 | if ((crc & 0x0001) != 0) { // If the LSB is set 33 | crc >>= 1; // Shift right and XOR 0xA001 34 | crc ^= 0xA001; 35 | } 36 | else // Else LSB is not set 37 | crc >>= 1; // Just shift right 38 | } // Just shift right 39 | } 40 | return crc; 41 | } 42 | 43 | bool Config::CheckCRC(void) 44 | { 45 | return eeprom_crc(0) == 0; 46 | } 47 | 48 | void Config::UpdateCRC(void) 49 | { 50 | uint16_t crc = eeprom_crc(2); 51 | int p = EEPROM.length() - 2; 52 | EEPROM[p++] = crc & 0xFF; 53 | EEPROM[p++] = crc >> 8; 54 | } 55 | 56 | void IP_Config_t::SaveConfig() 57 | { 58 | int p = 0; 59 | version = _version; 60 | EEWRITE(version); 61 | EEWRITE(useDHCP); 62 | EEWRITE(mac_address); 63 | EEWRITE((uint32_t)local_ip); 64 | EEWRITE((uint32_t)dns_server); 65 | EEWRITE((uint32_t)gateway); 66 | EEWRITE((uint32_t)subnet); 67 | EEWRITE(modbus_baudrate); 68 | UpdateCRC(); 69 | } 70 | 71 | void IP_Config_t::LoadConfig() 72 | { 73 | int p = 0; 74 | uint32_t buf; 75 | 76 | if (!CheckCRC()) return; 77 | EEREAD(version); 78 | if (version != _version) return; 79 | 80 | EEREAD(useDHCP); 81 | EEREAD(mac_address); 82 | EEREAD(buf); 83 | local_ip = buf; 84 | EEREAD(buf); 85 | dns_server = buf; 86 | EEREAD(buf); 87 | gateway = buf; 88 | EEREAD(buf); 89 | subnet = buf; 90 | EEREAD(modbus_baudrate); 91 | } 92 | 93 | void IP_Config_t::ParseConfig(StringParse &buf) 94 | { 95 | useDHCP = buf.Get(F("useDHCP")) == "on" ? true : false; 96 | Ascii2MAC(buf.Get(F("mac")), mac_address); 97 | Ascii2IP(buf.Get(F("ip")), local_ip); 98 | Ascii2IP(buf.Get(F("subnet")), subnet); 99 | Ascii2IP(buf.Get(F("gateway")), gateway); 100 | Ascii2IP(buf.Get(F("dns")), dns_server); 101 | modbus_baudrate = buf.Get(F("modbus_baudrate")).toInt(); 102 | } 103 | 104 | String IP_Config_t::IP2Ascii(IPAddress ip) 105 | { 106 | char buf[16]; 107 | sprintf(buf, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); 108 | return String(buf); 109 | } 110 | 111 | void IP_Config_t::Ascii2IP(String str, IPAddress &ip) 112 | { 113 | sscanf(str.c_str(), "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3]); 114 | } 115 | 116 | String IP_Config_t::MAC2Ascii(uint8_t *mac) 117 | { 118 | char buf[18]; 119 | sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 120 | return String(buf); 121 | } 122 | 123 | void IP_Config_t::Ascii2MAC(String str, uint8_t *mac) 124 | { 125 | sscanf(str.c_str(), "%02X:%02X:%02X:%02X:%02X:%02X", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]); 126 | } 127 | 128 | String IP_Config_t::toXML(void) 129 | { 130 | xmlstring str; 131 | str += F("\n"); 132 | str += F("\n"); 133 | str.catTag(F("useDHCP"), useDHCP); 134 | str.catTag(F("mac"), MAC2Ascii(mac_address)); 135 | str.catTag(F("ip"), IP2Ascii(local_ip)); 136 | str.catTag(F("subnet"), IP2Ascii(subnet)); 137 | str.catTag(F("dns"), IP2Ascii(dns_server)); 138 | str.catTag(F("gateway"), IP2Ascii(gateway)); 139 | str.catTag(F("modbus_baudrate"), String(modbus_baudrate)); 140 | str += F("\n"); 141 | return str; 142 | } -------------------------------------------------------------------------------- /Config.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Frederic Rible 2 | // 3 | // This file is part of LDuino, an Arduino based PLC software compatible with LDmicro. 4 | // 5 | // LDuino is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // LDuino is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with LDmicro. If not, see . 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #define EEWRITE(v) { EEPROM.put(p, v); p += sizeof(v); } 25 | #define EEREAD(v) { EEPROM.get(p, v); p += sizeof(v); } 26 | 27 | #define PROGRAM_OFFSET 32 // The PLC program will be stored in EEPROM starting from this address 28 | 29 | class StringParse : public String 30 | { 31 | public: 32 | String Get(String key) { 33 | int b = this->indexOf(key); 34 | if (b < 0) return ""; 35 | b = this->indexOf('=', b); 36 | if (b < 0) return ""; 37 | b++; 38 | int e = this->indexOf('\r', b); 39 | if (e < 0) return ""; 40 | return this->substring(b, e); 41 | } 42 | }; 43 | 44 | 45 | class Config { 46 | public: 47 | bool CheckCRC(void); 48 | void UpdateCRC(void); 49 | private: 50 | uint16_t eeprom_crc(int minus = 0); 51 | }; 52 | 53 | class IP_Config_t : public Config { 54 | public: 55 | bool useDHCP = true; 56 | uint8_t mac_address[6] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; 57 | IPAddress local_ip = { 192, 168, 1, 241 }; 58 | IPAddress dns_server = { 192, 168, 1, 1 }; 59 | IPAddress gateway = { 192, 168, 1, 1 }; 60 | IPAddress subnet = { 255, 255, 255, 0 }; 61 | uint32_t modbus_baudrate = 9600; 62 | void SaveConfig(); 63 | void LoadConfig(); 64 | void ParseConfig(StringParse & buf); 65 | static String IP2Ascii(IPAddress ip); 66 | static void Ascii2IP(String str, IPAddress & ip); 67 | static String MAC2Ascii(uint8_t * mac); 68 | static void Ascii2MAC(String str, uint8_t * mac); 69 | String toXML(void); 70 | private: 71 | uint16_t version; 72 | const uint16_t _version = 0x12F8; 73 | }; -------------------------------------------------------------------------------- /IO.ino: -------------------------------------------------------------------------------- 1 | /* Programmable Logic Controller Library for the Arduino and Compatibles 2 | 3 | Controllino Maxi PLC - Use of default pin names and numbers 4 | Product information: http://controllino.cc 5 | 6 | Connections: 7 | Inputs connected to pins A0 - A9, plus interrupts IN0 and IN1 8 | Digital outputs connected to pins D0 to D11 9 | Relay outputs connected to pins R0 to R9 10 | 11 | Software and Documentation: 12 | http://www.electronics-micros.com/software-hardware/plclib-arduino/ 13 | 14 | */ 15 | 16 | // Pins A0 - A9 are configured automatically 17 | 18 | // Interrupt pins 19 | const int IN0 = 18; 20 | const int IN1 = 19; 21 | 22 | const int D0 = 2; 23 | const int D1 = 3; 24 | const int D2 = 4; 25 | const int D3 = 5; 26 | const int D4 = 6; 27 | const int D5 = 7; 28 | const int D6 = 8; 29 | const int D7 = 9; 30 | const int D8 = 10; 31 | const int D9 = 11; 32 | const int D10 = 12; 33 | const int D11 = 13; 34 | 35 | const int R0 = 22; 36 | const int R1 = 23; 37 | const int R2 = 24; 38 | const int R3 = 25; 39 | const int R4 = 26; 40 | const int R5 = 27; 41 | const int R6 = 28; 42 | const int R7 = 29; 43 | const int R8 = 30; 44 | const int R9 = 31; 45 | 46 | void customIO() { 47 | // Input pin directions 48 | pinMode(A0, INPUT); 49 | pinMode(A1, INPUT); 50 | pinMode(A2, INPUT); 51 | pinMode(A3, INPUT); 52 | pinMode(A4, INPUT); 53 | pinMode(A5, INPUT); 54 | pinMode(A6, INPUT); 55 | pinMode(A7, INPUT); 56 | pinMode(A8, INPUT); 57 | pinMode(A9, INPUT); 58 | 59 | // Output pin directions 60 | pinMode(D0, OUTPUT); 61 | pinMode(D1, OUTPUT); 62 | pinMode(D2, OUTPUT); 63 | pinMode(D3, OUTPUT); 64 | pinMode(D4, OUTPUT); 65 | pinMode(D5, OUTPUT); 66 | pinMode(D6, OUTPUT); 67 | pinMode(D7, OUTPUT); 68 | pinMode(D8, OUTPUT); 69 | pinMode(D9, OUTPUT); 70 | pinMode(D10, OUTPUT); 71 | pinMode(D11, OUTPUT); 72 | 73 | // Relay pin directions 74 | pinMode(R0, OUTPUT); 75 | pinMode(R1, OUTPUT); 76 | pinMode(R2, OUTPUT); 77 | pinMode(R3, OUTPUT); 78 | pinMode(R4, OUTPUT); 79 | pinMode(R5, OUTPUT); 80 | pinMode(R6, OUTPUT); 81 | pinMode(R7, OUTPUT); 82 | pinMode(R8, OUTPUT); 83 | pinMode(R9, OUTPUT); 84 | 85 | } 86 | 87 | -------------------------------------------------------------------------------- /LDuino.ino: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Frederic Rible 2 | // 3 | // This file is part of LDuino, an Arduino based PLC software compatible with LDmicro. 4 | // 5 | // LDuino is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // LDuino is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with LDmicro. If not, see . 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define USE_RTOS 1 26 | 27 | #ifdef USE_RTOS 28 | #include 29 | #include 30 | SemaphoreHandle_t plcSemaphore = NULL; 31 | #endif 32 | 33 | #define noPinDefs // Disable default pin definitions (X0, X1, ..., Y0, Y1, ...) 34 | 35 | #include 36 | 37 | #ifdef CONTROLLINO_MAXI 38 | #include 39 | #include 40 | #else 41 | #include 42 | #define SDCARD_CS 4 43 | #endif 44 | #include 45 | 46 | #include "Modbus.h" 47 | #include "ModbusIP.h" 48 | #include "ModbusRelay.h" 49 | #include "plcweb.h" 50 | #include "lduino_engine.h" 51 | #include "Config.h" 52 | #include "sysinfo.h" 53 | 54 | //ModbusIP object 55 | ModbusIP mb; 56 | 57 | // LDmicro Ladder interpreter 58 | LDuino_engine lduino; 59 | IP_Config_t IP_Config;; 60 | bool doReset = false; 61 | 62 | #ifdef CONTROLLINO_MAXI 63 | void switch_txrx(ModbusRelay::txrx_mode mode) 64 | { 65 | switch (mode) { 66 | case ModbusRelay::tx: 67 | Controllino_SwitchRS485DE(1); 68 | Controllino_SwitchRS485RE(1); 69 | break; 70 | case ModbusRelay::rx: 71 | Controllino_SwitchRS485DE(0); 72 | Controllino_SwitchRS485RE(0); 73 | break; 74 | default: 75 | Controllino_SwitchRS485DE(0); 76 | Controllino_SwitchRS485RE(1); 77 | break; 78 | } 79 | } 80 | #endif 81 | 82 | void setup_MODBUS() 83 | { 84 | mb.config(); //Config Modbus IP 85 | #ifdef CONTROLLINO_MAXI 86 | Controllino_RS485Init(); 87 | mb.configRelay(&Serial3, IP_Config.modbus_baudrate, SERIAL_8N1, switch_txrx); 88 | #else 89 | mb.configRelay(&Serial3, IP_Config.modbus_baudrate, SERIAL_8N1, NULL); 90 | #endif 91 | lduino.SetModbus(&mb); 92 | } 93 | 94 | 95 | void pollPLC() 96 | { 97 | #ifndef USE_RTOS 98 | lduino.Engine(); 99 | #else 100 | xSemaphoreGiveFromISR(plcSemaphore, NULL); 101 | #endif 102 | } 103 | 104 | extern void(*_malloc_exception)(size_t); 105 | 106 | void malloc_exception(size_t len) 107 | { 108 | Serial.print(F("*** malloc error len=")); 109 | Serial.println(len); 110 | } 111 | 112 | void setup() { 113 | _malloc_exception = malloc_exception; 114 | __malloc_heap_end = (char*)RAMEND; 115 | sysinfo::wipeRam(); 116 | 117 | Serial.begin(115200); 118 | Serial << F("Setup\n"); 119 | 120 | IP_Config.LoadConfig(); 121 | 122 | #ifndef CONTROLLINO_MAXI 123 | pinMode(SDCARD_CS, OUTPUT); 124 | digitalWrite(SDCARD_CS, HIGH);//Deselect the SD card 125 | #endif 126 | 127 | if (IP_Config.useDHCP) { 128 | Serial << F("Trying DHCP ...\n"); 129 | if (!Ethernet.begin(IP_Config.mac_address)) { 130 | Serial << F("DHCP failure\n"); 131 | } 132 | else { 133 | Serial << F("DHCP ok\n"); 134 | IP_Config.local_ip = Ethernet.localIP(); 135 | IP_Config.subnet = Ethernet.subnetMask(); 136 | IP_Config.dns_server = Ethernet.dnsServerIP(); 137 | IP_Config.gateway = Ethernet.gatewayIP(); 138 | } 139 | } 140 | 141 | if (!IP_Config.useDHCP || Ethernet.localIP() == INADDR_NONE) { 142 | Ethernet.begin(IP_Config.mac_address, IP_Config.local_ip, IP_Config.dns_server, IP_Config.gateway, IP_Config.subnet); 143 | } 144 | 145 | Serial << F("IP: ") << Ethernet.localIP() << '\n'; 146 | 147 | customIO(); // Setup inputs and outputs for Controllino PLC 148 | setup_MODBUS(); 149 | 150 | #ifdef USE_RTOS 151 | plcSemaphore = xSemaphoreCreateBinary(); 152 | 153 | xTaskCreate( 154 | Task_Net 155 | , (const portCHAR *) "Net" 156 | , 500 // Stack size 157 | , NULL 158 | , 0 // Priority 159 | , NULL); 160 | 161 | xTaskCreate( 162 | Task_Modbus 163 | , (const portCHAR *) "Modbus" 164 | , 150 // Stack size 165 | , NULL 166 | , 2 // Priority 167 | , NULL); 168 | 169 | xTaskCreate( 170 | Task_PLC 171 | , (const portCHAR *)"PLC" 172 | , 150 173 | , NULL 174 | , 1 175 | , NULL); 176 | #endif 177 | 178 | Timer1.initialize(lduino.getCycleMS() * 1000L); 179 | Timer1.attachInterrupt(pollPLC); 180 | 181 | Serial << F("unusedRam ") << sysinfo::unusedRam() << '\n'; 182 | Serial << F("PLC ready\n"); 183 | lduino.setStatus(F("Ready")); 184 | } 185 | 186 | #ifdef USE_RTOS 187 | void Task_PLC(void *pvParameters) 188 | { 189 | Serial << F("PLC task started\n"); 190 | 191 | for (;;) // A Task shall never return or exit. 192 | { 193 | if (xSemaphoreTake(plcSemaphore, portMAX_DELAY) == pdTRUE) lduino.Engine(); 194 | } 195 | } 196 | 197 | void Task_Modbus(void *pvParameters) 198 | { 199 | Serial << F("Modbus task started\n"); 200 | 201 | for (;;) // A Task shall never return or exit. 202 | { 203 | mb.pollSerial(); 204 | vTaskDelay(1); // 100 / portTICK_PERIOD_MS; 205 | } 206 | } 207 | 208 | void Task_Net(void *pvParameters) 209 | { 210 | Serial << F("Task Net started\n"); 211 | 212 | setup_PLC_Web(); // Web server init 213 | 214 | for (;;) // A Task shall never return or exit. 215 | { 216 | mb.pollTCP(); 217 | poll_PLC_Web(); 218 | vTaskDelay(1); // 100 / portTICK_PERIOD_MS; 219 | } 220 | } 221 | 222 | void vApplicationMallocFailedHook(void) 223 | { 224 | Serial.println(F("*** vmalloc error")); 225 | } 226 | #endif 227 | 228 | void loop() { 229 | #ifndef USE_RTOS 230 | mb.pollTCP(); 231 | mb.pollSerial(); 232 | poll_PLC_Web(); 233 | #endif 234 | 235 | 236 | //lduino.Engine(); 237 | if (doReset) { 238 | Serial << F("Stack usage ") << sysinfo::stackUsage() << '\n'; 239 | Serial << sysinfo::DumpRTOS(); 240 | Serial << F("Reset requested\n"); 241 | delay(500); 242 | #ifndef USE_RTOS 243 | Timer1.stop(); 244 | #endif 245 | noInterrupts(); 246 | #ifdef CONTROLLINO_MAXI 247 | pinMode(45, OUTPUT); 248 | digitalWrite(45, 0); // X2 connector bridge between pin 6 and 11 249 | #else 250 | wdt_disable(); 251 | wdt_enable(WDTO_1S); 252 | #endif 253 | doReset = false; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /LDuino.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LDuino", "LDuino.vcxproj", "{741C0DAA-3534-4AD7-9EDF-CFC4E58E98E7}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LDmicro", "..\LDmicro\ldmicro\VisualStudio\LDmicro.vcxproj", "{934C13CD-BFB1-4664-8431-C7A6804F5C0E}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ModbusSlaveSimu", "..\ModbusSlaveSimu\ModbusSlaveSimu.vcxproj", "{22083D63-7769-4E38-9EEE-453CEC4D6B52}" 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "streaming_example", "..\..\..\..\ARDUINO\Arduino\libraries\Streaming\Examples\streaming_example\streaming_example.vcxproj", "{4BB10303-4603-4D9B-9A05-75B5DDE89FC4}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|x64 = Debug|x64 17 | Debug|x86 = Debug|x86 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {741C0DAA-3534-4AD7-9EDF-CFC4E58E98E7}.Debug|x64.ActiveCfg = Debug|x64 23 | {741C0DAA-3534-4AD7-9EDF-CFC4E58E98E7}.Debug|x64.Build.0 = Debug|x64 24 | {741C0DAA-3534-4AD7-9EDF-CFC4E58E98E7}.Debug|x86.ActiveCfg = Debug|Win32 25 | {741C0DAA-3534-4AD7-9EDF-CFC4E58E98E7}.Debug|x86.Build.0 = Debug|Win32 26 | {741C0DAA-3534-4AD7-9EDF-CFC4E58E98E7}.Release|x64.ActiveCfg = Release|x64 27 | {741C0DAA-3534-4AD7-9EDF-CFC4E58E98E7}.Release|x64.Build.0 = Release|x64 28 | {741C0DAA-3534-4AD7-9EDF-CFC4E58E98E7}.Release|x86.ActiveCfg = Release|Win32 29 | {741C0DAA-3534-4AD7-9EDF-CFC4E58E98E7}.Release|x86.Build.0 = Release|Win32 30 | {934C13CD-BFB1-4664-8431-C7A6804F5C0E}.Debug|x64.ActiveCfg = Debug|x64 31 | {934C13CD-BFB1-4664-8431-C7A6804F5C0E}.Debug|x64.Build.0 = Debug|x64 32 | {934C13CD-BFB1-4664-8431-C7A6804F5C0E}.Debug|x86.ActiveCfg = Debug|Win32 33 | {934C13CD-BFB1-4664-8431-C7A6804F5C0E}.Debug|x86.Build.0 = Debug|Win32 34 | {934C13CD-BFB1-4664-8431-C7A6804F5C0E}.Release|x64.ActiveCfg = Release|x64 35 | {934C13CD-BFB1-4664-8431-C7A6804F5C0E}.Release|x64.Build.0 = Release|x64 36 | {934C13CD-BFB1-4664-8431-C7A6804F5C0E}.Release|x86.ActiveCfg = Release|Win32 37 | {934C13CD-BFB1-4664-8431-C7A6804F5C0E}.Release|x86.Build.0 = Release|Win32 38 | {22083D63-7769-4E38-9EEE-453CEC4D6B52}.Debug|x64.ActiveCfg = Debug|x64 39 | {22083D63-7769-4E38-9EEE-453CEC4D6B52}.Debug|x64.Build.0 = Debug|x64 40 | {22083D63-7769-4E38-9EEE-453CEC4D6B52}.Debug|x86.ActiveCfg = Debug|Win32 41 | {22083D63-7769-4E38-9EEE-453CEC4D6B52}.Debug|x86.Build.0 = Debug|Win32 42 | {22083D63-7769-4E38-9EEE-453CEC4D6B52}.Release|x64.ActiveCfg = Release|x64 43 | {22083D63-7769-4E38-9EEE-453CEC4D6B52}.Release|x64.Build.0 = Release|x64 44 | {22083D63-7769-4E38-9EEE-453CEC4D6B52}.Release|x86.ActiveCfg = Release|Win32 45 | {22083D63-7769-4E38-9EEE-453CEC4D6B52}.Release|x86.Build.0 = Release|Win32 46 | {4BB10303-4603-4D9B-9A05-75B5DDE89FC4}.Debug|x64.ActiveCfg = Debug|x64 47 | {4BB10303-4603-4D9B-9A05-75B5DDE89FC4}.Debug|x64.Build.0 = Debug|x64 48 | {4BB10303-4603-4D9B-9A05-75B5DDE89FC4}.Debug|x86.ActiveCfg = Debug|Win32 49 | {4BB10303-4603-4D9B-9A05-75B5DDE89FC4}.Debug|x86.Build.0 = Debug|Win32 50 | {4BB10303-4603-4D9B-9A05-75B5DDE89FC4}.Release|x64.ActiveCfg = Release|x64 51 | {4BB10303-4603-4D9B-9A05-75B5DDE89FC4}.Release|x64.Build.0 = Release|x64 52 | {4BB10303-4603-4D9B-9A05-75B5DDE89FC4}.Release|x86.ActiveCfg = Release|Win32 53 | {4BB10303-4603-4D9B-9A05-75B5DDE89FC4}.Release|x86.Build.0 = Release|Win32 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | EndGlobal 59 | -------------------------------------------------------------------------------- /Modbus.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1oat/LDuino/d054cdcccded12e4b40e457f0288768c55a742d7/Modbus.cpp -------------------------------------------------------------------------------- /Modbus.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1oat/LDuino/d054cdcccded12e4b40e457f0288768c55a742d7/Modbus.h -------------------------------------------------------------------------------- /ModbusIP.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusIP.cpp - Source for Modbus IP Library 3 | Copyright (C) 2015 André Sarmento Barbosa 4 | 5 | Add support for RS485 relay (by Frederic RIBLE) 6 | */ 7 | #include "ModbusIP.h" 8 | #include "ModbusRelay.h" 9 | 10 | ModbusIP::ModbusIP():_server(MODBUSIP_PORT) { 11 | this->_slaveId= 1; 12 | this->client = NULL; 13 | } 14 | 15 | void ModbusIP::config() { 16 | _server.begin(); 17 | } 18 | 19 | void ModbusIP::config(uint8_t *mac) { 20 | Ethernet.begin(mac); 21 | _server.begin(); 22 | } 23 | 24 | void ModbusIP::config(uint8_t *mac, IPAddress ip) { 25 | Ethernet.begin(mac, ip); 26 | _server.begin(); 27 | } 28 | 29 | void ModbusIP::config(uint8_t *mac, IPAddress ip, IPAddress dns) { 30 | Ethernet.begin(mac, ip, dns); 31 | _server.begin(); 32 | } 33 | 34 | void ModbusIP::config(uint8_t *mac, IPAddress ip, IPAddress dns, IPAddress gateway) { 35 | Ethernet.begin(mac, ip, dns, gateway); 36 | _server.begin(); 37 | } 38 | 39 | void ModbusIP::config(uint8_t *mac, IPAddress ip, IPAddress dns, IPAddress gateway, IPAddress subnet) { 40 | Ethernet.begin(mac, ip, dns, gateway, subnet); 41 | _server.begin(); 42 | } 43 | 44 | void ModbusIP::configRelay(HardwareSerial* port, long baud, u_int format, void (*_switch_txrx)(ModbusRelay::txrx_mode)) { 45 | _relay.configRelay(port, baud, format, _switch_txrx); 46 | } 47 | 48 | void ModbusIP::pollSerial() 49 | { 50 | _relay.pollSerial(); 51 | } 52 | 53 | void ModbusIP::pollTCP() 54 | { 55 | _relay.pollTCP(); 56 | 57 | client = _server.available(); 58 | 59 | if (client) { 60 | if (client.connected()) { 61 | int i = 0; 62 | while (client.available()){ 63 | _MBAP[i] = client.read(); 64 | i++; 65 | if (i==7) break; //MBAP length has 7 bytes size 66 | } 67 | _len = _MBAP[4] << 8 | _MBAP[5]; 68 | _len--; // Do not count with last byte from MBAP 69 | 70 | if (_MBAP[2] !=0 || _MBAP[3] !=0) return; //Not a MODBUSIP packet 71 | if (_len > MODBUSIP_MAXFRAME) return; //Length is over MODBUSIP_MAXFRAME 72 | 73 | _frame = (byte*) malloc(_len); 74 | i = 0; 75 | while (client.available()){ 76 | _frame[i] = client.read(); 77 | i++; 78 | if (i==_len) break; 79 | } 80 | 81 | if (_MBAP[6] == this->_slaveId) { // This MODBUS frame is for us 82 | this->receivePDU(_frame); 83 | if (_reply != MB_REPLY_OFF) { 84 | //MBAP 85 | _MBAP[4] = (_len+1) >> 8; //_len+1 for last byte from MBAP 86 | _MBAP[5] = (_len+1) & 0x00FF; 87 | 88 | byte sendbuffer[7 + _len]; 89 | 90 | for (i = 0 ; i < 7 ; i++) { 91 | sendbuffer[i] = _MBAP[i]; 92 | } 93 | //PDU Frame 94 | for (i = 0 ; i < _len ; i++) { 95 | sendbuffer[i+7] = _frame[i]; 96 | } 97 | client.write(sendbuffer, _len + 7); 98 | } 99 | } 100 | else { // Relay over RS485 Serial line 101 | _relay.TX(client, _MBAP, _frame, _len); 102 | } 103 | #ifndef TCP_KEEP_ALIVE 104 | client.stop(); 105 | #endif 106 | free(_frame); 107 | _len = 0; 108 | } 109 | } 110 | } 111 | 112 | /* Table of CRC values for high–order byte */ 113 | const byte _auchCRCHi[] = { 114 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 115 | 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 116 | 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 117 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 118 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 119 | 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 120 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 121 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 122 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 123 | 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 124 | 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 125 | 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 126 | 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 127 | 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 128 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 129 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 130 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 131 | 0x40}; 132 | 133 | /* Table of CRC values for low–order byte */ 134 | const byte _auchCRCLo[] = { 135 | 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 136 | 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 137 | 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 138 | 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 139 | 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 140 | 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 141 | 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 142 | 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 143 | 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 144 | 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 145 | 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 146 | 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 147 | 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 148 | 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 149 | 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 150 | 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 151 | 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 152 | 0x40}; 153 | 154 | word ModbusIP::calcCrc(byte address, byte* pduFrame, byte pduLen) { 155 | byte CRCHi = 0xFF, CRCLo = 0x0FF, Index; 156 | 157 | Index = CRCHi ^ address; 158 | CRCHi = CRCLo ^ _auchCRCHi[Index]; 159 | CRCLo = _auchCRCLo[Index]; 160 | 161 | while (pduLen--) { 162 | Index = CRCHi ^ *pduFrame++; 163 | CRCHi = CRCLo ^ _auchCRCHi[Index]; 164 | CRCLo = _auchCRCLo[Index]; 165 | } 166 | 167 | return (CRCHi << 8) | CRCLo; 168 | } -------------------------------------------------------------------------------- /ModbusIP.h: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusIP.h - Header for Modbus IP Library 3 | Copyright (C) 2015 André Sarmento Barbosa 4 | */ 5 | #include 6 | #include 7 | 8 | #ifdef CONTROLLINO_MAXI 9 | #include 10 | #else 11 | #include 12 | #endif 13 | 14 | #include "Modbus.h" 15 | #include "ModbusRelay.h" 16 | 17 | #ifndef MODBUSIP_H 18 | #define MODBUSIP_H 19 | 20 | #define MODBUSIP_PORT 502 21 | #define MODBUSIP_MAXFRAME 200 22 | 23 | #define TCP_KEEP_ALIVE 24 | 25 | //#define TCP_KEEP_ALIVE 26 | 27 | class ModbusIP : public Modbus { 28 | public: 29 | ModbusIP(); 30 | void config(); 31 | void config(uint8_t *mac); 32 | void config(uint8_t *mac, IPAddress ip); 33 | void config(uint8_t *mac, IPAddress ip, IPAddress dns); 34 | void config(uint8_t *mac, IPAddress ip, IPAddress dns, IPAddress gateway); 35 | void config(uint8_t *mac, IPAddress ip, IPAddress dns, IPAddress gateway, IPAddress subnet); 36 | void configRelay(HardwareSerial* port, long baud, u_int format, void (*_switch_txrx)(ModbusRelay::txrx_mode)); 37 | void pollTCP(); 38 | void pollSerial(); 39 | private: 40 | EthernetServer _server; 41 | byte _MBAP[7]; 42 | EthernetClient client; 43 | 44 | // Additional objects for Serial port relaying of traffic not managed locally 45 | byte _slaveId;; // Modbus address locally managed 46 | word calcCrc(byte address, byte* pduframe, byte pdulen); 47 | 48 | ModbusRelay _relay; 49 | }; 50 | 51 | #endif //MODBUSIP_H 52 | 53 | -------------------------------------------------------------------------------- /ModbusRelay.cpp: -------------------------------------------------------------------------------- 1 | #include "ModbusRelay.h" 2 | #include 3 | #include 4 | 5 | ModbusRelay::ModbusRelay() 6 | { 7 | _SerialInProgress = false; 8 | client = NULL; 9 | ModbusTimeout_ms = 100; 10 | stats_crc_error = 0; 11 | stats_timeout = 0; 12 | stats_transaction = 0; 13 | stats_short_frame = 0; 14 | _tcp_len = 0; 15 | } 16 | 17 | String ModbusRelay::DumpStats() 18 | { 19 | String str; 20 | str += F("[Transactions="); 21 | str += stats_transaction; 22 | str += F(" crc="); 23 | str += stats_crc_error; 24 | str += F(" timeouts="); 25 | str += stats_timeout; 26 | str += F(" short="); 27 | str += stats_short_frame; 28 | str += ']'; 29 | return str; 30 | } 31 | 32 | void ModbusRelay::configRelay(HardwareSerial* port, long baud, u_int format, void(*_switch_txrx)(txrx_mode)) 33 | { 34 | this->_port = port; 35 | port->begin(baud, format); 36 | this->_switch_txrx = _switch_txrx; 37 | 38 | if (baud > 19200) { 39 | _t15 = 750; 40 | _t35 = 1750; 41 | } 42 | else { 43 | _t15 = 15000000 / baud; // 1T * 1.5 = T1.5 44 | _t35 = 35000000 / baud; // 1T * 3.5 = T3.5 45 | } 46 | Serial.print(F("t_15=")); 47 | Serial.println(_t15); 48 | Serial.print(F("t_35=")); 49 | Serial.println(_t35); 50 | } 51 | 52 | bool ModbusRelay::pollSerial() 53 | { 54 | if (!_SerialInProgress) return false; 55 | 56 | if (micros() > _timeoutTransaction) { 57 | stats_timeout++; 58 | Serial << F("*** RS485: timeout available=") << _port->available() << F(" _len=") << _len << ' ' << DumpStats() << '\n'; 59 | _len = 0; 60 | _SerialInProgress = false; 61 | while (_port->available()) _port->read(); 62 | // Switch off receiver 63 | if (_switch_txrx) _switch_txrx(off); 64 | return false; 65 | } 66 | 67 | if (_port->available() > _len) { // We have received new data 68 | _len = _port->available(); 69 | _timeoutFrame = micros() + _t35; 70 | } 71 | else if (_len > 1 && micros() > _timeoutFrame) { 72 | if (_len >= 3) { 73 | byte i; 74 | _rxid = _port->read(); 75 | _len--; 76 | if (_rxid == 0 && _len > 0) { // Remove random parasitic zero after remote switch TX on 77 | _rxid = _port->read(); 78 | _len--; 79 | } 80 | _frame = (byte*)malloc(7+_len); // Add 7 byte for future NBAP header 81 | for (i = 0; i < _len; i++) { 82 | _frame[7+i] = _port->read(); 83 | } 84 | RX(); 85 | } 86 | else { 87 | stats_short_frame++; 88 | Serial << F("*** RS485: short frame ") << _len << ' ' << DumpStats() << '\n'; 89 | } 90 | while (_port->available()) _port->read(); 91 | _len = 0; 92 | _SerialInProgress = false; 93 | // Switch off receiver 94 | if (_switch_txrx) _switch_txrx(off); 95 | } 96 | 97 | return _SerialInProgress; 98 | } 99 | 100 | void ModbusRelay::TX(EthernetClient client, byte MBAP[], byte *frame, byte len) 101 | { 102 | if (_SerialInProgress) return; // We cannot have two transactions at the same time 103 | 104 | //Serial.print("RS485 TX ID="); 105 | //Serial.print(MBAP[6]); 106 | //Serial.print(" F="); 107 | //Serial.println(frame[0]); 108 | //Serial.println(len); 109 | 110 | memcpy(_MBAP, MBAP, 7); 111 | this->client = client; 112 | 113 | // Store function cocde rto report exception if neeeded 114 | this->_fc = frame[0]; 115 | 116 | // Switch to TX mode 117 | if (_switch_txrx) _switch_txrx(tx);; 118 | 119 | //Send slaveId 120 | _port->write(MBAP[6]); 121 | 122 | //Send PDU 123 | byte i; 124 | for (i = 0; i < len; i++) { 125 | _port->write(frame[i]); 126 | } 127 | 128 | //Send CRC 129 | word crc = calcCrc(MBAP[6], frame, len); 130 | _port->write(crc >> 8); 131 | _port->write(crc & 0xFF); 132 | 133 | _port->flush(); 134 | //delayMicroseconds(_t35); 135 | 136 | // Switch to RX mode 137 | if (_switch_txrx) _switch_txrx(rx); 138 | 139 | _len = 0; 140 | _timeoutTransaction = micros() + ModbusTimeout_ms * 1000L; 141 | _timeoutFrame = 0; 142 | _SerialInProgress = true; 143 | stats_transaction++; 144 | } 145 | 146 | bool ModbusRelay::RX() 147 | { 148 | //Serial.print(F("RS485 RX ")); 149 | //Serial.println(_len); 150 | 151 | //Last two bytes = crc 152 | u_int crc = ((_frame[7+_len - 2] << 8) | _frame[7+_len - 1]); 153 | 154 | //CRC Check 155 | if (crc != calcCrc(_rxid, _frame+7, _len - 2)) { 156 | stats_crc_error++; 157 | Serial.print(F("*** RS485: CRC error ")); 158 | for (byte i=0; i<_len; i++) { 159 | Serial.print(_frame[7+i]); 160 | Serial.print(' '); 161 | } 162 | Serial << ' ' << DumpStats() << '\n'; 163 | // Report exception "slave device failure" 164 | _frame[7] = _fc | 0x80; 165 | _frame[8] = 0x04; 166 | _len = 2; 167 | } 168 | else { 169 | _MBAP[6] = _rxid; 170 | _len -= 2; // remove CRC 171 | } 172 | 173 | //MBAP 174 | _MBAP[4] = (_len + 1) >> 8; //_len+1 for last byte from MBAP 175 | _MBAP[5] = (_len + 1) & 0x00FF; 176 | 177 | for (int i = 0; i < 7; i++) { 178 | _frame[i] = _MBAP[i]; 179 | } 180 | _tcp_len = _len + 7; // Signal we have some tcp data to transfer 181 | return true; 182 | } 183 | 184 | void ModbusRelay::pollTCP() 185 | { 186 | if (!_tcp_len) return; 187 | if (client) { 188 | client.write(_frame, _tcp_len); 189 | } 190 | free(_frame); 191 | _tcp_len = 0; 192 | } 193 | -------------------------------------------------------------------------------- /ModbusRelay.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef CONTROLLINO_MAXI 6 | #include 7 | #else 8 | #include 9 | #endif 10 | 11 | #include "ModbusSerial.h" 12 | 13 | class ModbusRelay : public ModbusSerial { 14 | private: 15 | unsigned int ModbusTimeout_ms; // Modbus timeout for Serial transactions in ms 16 | 17 | bool _SerialInProgress; // True when a transaction is in progress over the serial port* 18 | unsigned long _timeoutTransaction; 19 | unsigned long _timeoutFrame; 20 | 21 | byte _MBAP[7]; 22 | byte *_frame; 23 | byte _len; 24 | byte _tcp_len; 25 | EthernetClient client; 26 | byte _fc; // Function code of on-going transaction 27 | byte _rxid; // Received slaveID 28 | 29 | bool RX(); 30 | 31 | short stats_transaction; 32 | short stats_crc_error; 33 | short stats_timeout; 34 | short stats_short_frame; 35 | public: 36 | 37 | ModbusRelay(); 38 | typedef enum { off, tx, rx } txrx_mode; 39 | void(*_switch_txrx)(txrx_mode); 40 | 41 | void configRelay(HardwareSerial * port, long baud, u_int format, void(*_switch_txrx)(txrx_mode)); 42 | void TX(EthernetClient client, byte MBAP[], byte * frame, byte len); 43 | bool pollSerial(); 44 | void pollTCP(); 45 | 46 | String DumpStats(); 47 | }; 48 | -------------------------------------------------------------------------------- /ModbusSerial.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusSerial.cpp - Source for Modbus Serial Library 3 | Copyright (C) 2014 André Sarmento Barbosa 4 | */ 5 | #include "ModbusSerial.h" 6 | 7 | ModbusSerial::ModbusSerial() { 8 | 9 | } 10 | 11 | bool ModbusSerial::setSlaveId(byte slaveId){ 12 | _slaveId = slaveId; 13 | return true; 14 | } 15 | 16 | byte ModbusSerial::getSlaveId() { 17 | return _slaveId; 18 | } 19 | 20 | bool ModbusSerial::config(HardwareSerial* port, long baud, u_int format, int txPin) { 21 | this->_port = port; 22 | this->_txPin = txPin; 23 | (*port).begin(baud, format); 24 | 25 | delay(2000); 26 | 27 | if (txPin >= 0) { 28 | pinMode(txPin, OUTPUT); 29 | digitalWrite(txPin, LOW); 30 | } 31 | 32 | if (baud > 19200) { 33 | _t15 = 750; 34 | _t35 = 1750; 35 | } else { 36 | _t15 = 15000000/baud; // 1T * 1.5 = T1.5 37 | _t35 = 35000000/baud; // 1T * 3.5 = T3.5 38 | } 39 | 40 | return true; 41 | } 42 | 43 | #ifdef USE_SOFTWARE_SERIAL 44 | bool ModbusSerial::config(SoftwareSerial* port, long baud, int txPin) { 45 | this->_port = port; 46 | this->_txPin = txPin; 47 | (*port).begin(baud); 48 | 49 | delay(2000); 50 | 51 | if (txPin >= 0) { 52 | pinMode(txPin, OUTPUT); 53 | digitalWrite(txPin, LOW); 54 | } 55 | 56 | if (baud > 19200) { 57 | _t15 = 750; 58 | _t35 = 1750; 59 | } else { 60 | _t15 = 15000000/baud; // 1T * 1.5 = T1.5 61 | _t35 = 35000000/baud; // 1T * 3.5 = T3.5 62 | } 63 | 64 | return true; 65 | } 66 | #endif 67 | 68 | #ifdef __AVR_ATmega32U4__ 69 | bool ModbusSerial::config(Serial_* port, long baud, u_int format, int txPin) { 70 | this->_port = port; 71 | this->_txPin = txPin; 72 | (*port).begin(baud, format); 73 | while (!(*port)); 74 | 75 | if (txPin >= 0) { 76 | pinMode(txPin, OUTPUT); 77 | digitalWrite(txPin, LOW); 78 | } 79 | 80 | if (baud > 19200) { 81 | _t15 = 750; 82 | _t35 = 1750; 83 | } else { 84 | _t15 = 15000000/baud; // 1T * 1.5 = T1.5 85 | _t35 = 35000000/baud; // 1T * 3.5 = T3.5 86 | } 87 | 88 | return true; 89 | } 90 | #endif 91 | 92 | bool ModbusSerial::receive(byte* frame) { 93 | //first byte of frame = address 94 | byte address = frame[0]; 95 | //Last two bytes = crc 96 | u_int crc = ((frame[_len - 2] << 8) | frame[_len - 1]); 97 | 98 | //Slave Check 99 | if (address != 0xFF && address != this->getSlaveId()) { 100 | return false; 101 | } 102 | 103 | //CRC Check 104 | if (crc != this->calcCrc(_frame[0], _frame+1, _len-3)) { 105 | return false; 106 | } 107 | 108 | //PDU starts after first byte 109 | //framesize PDU = framesize - address(1) - crc(2) 110 | this->receivePDU(frame+1); 111 | //No reply to Broadcasts 112 | if (address == 0xFF) _reply = MB_REPLY_OFF; 113 | return true; 114 | } 115 | 116 | bool ModbusSerial::send(byte* frame) { 117 | byte i; 118 | 119 | if (this->_txPin >= 0) { 120 | digitalWrite(this->_txPin, HIGH); 121 | delay(1); 122 | } 123 | 124 | for (i = 0 ; i < _len ; i++) { 125 | (*_port).write(frame[i]); 126 | } 127 | 128 | (*_port).flush(); 129 | delayMicroseconds(_t35); 130 | 131 | if (this->_txPin >= 0) { 132 | digitalWrite(this->_txPin, LOW); 133 | } 134 | } 135 | 136 | bool ModbusSerial::sendPDU(byte* pduframe) { 137 | if (this->_txPin >= 0) { 138 | digitalWrite(this->_txPin, HIGH); 139 | delay(1); 140 | } 141 | 142 | //Send slaveId 143 | (*_port).write(_slaveId); 144 | 145 | //Send PDU 146 | byte i; 147 | for (i = 0 ; i < _len ; i++) { 148 | (*_port).write(pduframe[i]); 149 | } 150 | 151 | //Send CRC 152 | word crc = calcCrc(_slaveId, _frame, _len); 153 | (*_port).write(crc >> 8); 154 | (*_port).write(crc & 0xFF); 155 | 156 | (*_port).flush(); 157 | delayMicroseconds(_t35); 158 | 159 | if (this->_txPin >= 0) { 160 | digitalWrite(this->_txPin, LOW); 161 | } 162 | } 163 | 164 | void ModbusSerial::task() { 165 | _len = 0; 166 | 167 | while ((*_port).available() > _len) { 168 | _len = (*_port).available(); 169 | delayMicroseconds(_t15); 170 | } 171 | 172 | if (_len == 0) return; 173 | 174 | byte i; 175 | _frame = (byte*) malloc(_len); 176 | for (i=0 ; i < _len ; i++) _frame[i] = (*_port).read(); 177 | 178 | if (this->receive(_frame)) { 179 | if (_reply == MB_REPLY_NORMAL) 180 | this->sendPDU(_frame); 181 | else 182 | if (_reply == MB_REPLY_ECHO) 183 | this->send(_frame); 184 | } 185 | 186 | free(_frame); 187 | _len = 0; 188 | } 189 | 190 | /* Table of CRC values for high–order byte */ 191 | static const byte _auchCRCHi[] = { 192 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 193 | 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 194 | 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 195 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 196 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 197 | 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 198 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 199 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 200 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 201 | 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 202 | 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 203 | 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 204 | 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 205 | 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 206 | 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 207 | 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 208 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 209 | 0x40 }; 210 | 211 | /* Table of CRC values for low–order byte */ 212 | static const byte _auchCRCLo[] = { 213 | 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 214 | 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 215 | 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 216 | 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 217 | 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 218 | 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 219 | 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 220 | 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 221 | 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 222 | 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 223 | 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 224 | 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 225 | 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 226 | 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 227 | 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 228 | 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 229 | 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 230 | 0x40 }; 231 | 232 | word ModbusSerial::calcCrc(byte address, byte* pduFrame, byte pduLen) { 233 | byte CRCHi = 0xFF, CRCLo = 0x0FF, Index; 234 | 235 | Index = CRCHi ^ address; 236 | CRCHi = CRCLo ^ _auchCRCHi[Index]; 237 | CRCLo = _auchCRCLo[Index]; 238 | 239 | while (pduLen--) { 240 | Index = CRCHi ^ *pduFrame++; 241 | CRCHi = CRCLo ^ _auchCRCHi[Index]; 242 | CRCLo = _auchCRCLo[Index]; 243 | } 244 | 245 | return (CRCHi << 8) | CRCLo; 246 | } -------------------------------------------------------------------------------- /ModbusSerial.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1oat/LDuino/d054cdcccded12e4b40e457f0288768c55a742d7/ModbusSerial.h -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LDuino 2 | (C) 2016-2018 Frederic RIBLE (f1oat@f1oat.org) 3 | 4 | LDuino is a PLC software originally designed for the "Controllino Maxi". 5 | It could be easily ported to any other Arduino based platform. 6 | 7 | It is based on a bytecode interpreter able to run code generated by a modified version LDmicro (https://github.com/f1oat/LDmicro). 8 | LDmicro is a Ladder logic editor. 9 | 10 | LDuino currently supports: 11 | * Ethernet Web interface for configuration management and status 12 | * Static IP address or DHCP 13 | * IP configuration stored in EEPROM 14 | * Ladder logic program bytecode stored in EEPROM 15 | * Digital or analog inputs named 'An' 16 | * Digital or PWM ouputs named 'Dn' 17 | * Relay outputs named 'Rn' 18 | * Most LDMicro Ladder logic blocks including timers and operators 19 | * MODBUS over IP slave mode 20 | * Coils and Holding registers (MODBUS slave ID = 1) 21 | * RS485 master gateway (when MODBUS slave ID <> 1 is used) 22 | * This gateway can be used to access RS485 slave devices connected to the PLC from a master connected by Ethernet 23 | 24 | [ MODBUS master ] -- Ethernet link -- [ Controllino PLC ] -- RS485 bus -- [ Additional slave devices ] 25 | 26 | Up to now, the PLC cannot act as a MODBUS master device. 27 | The PLC, as any other device connected to the RS485 PLC port, is a slave device controlled by the MODBUS master equipment 28 | (for example, a PC or a LinuxCNC/Machinekit board). 29 | 30 | The ldmicro/LDuino-valid.ld contains a sample Ladder program for LDuino and LDmicro validation. 31 | 32 | # Default IP configuration 33 | 34 | Buy default, LDuino software will try to get an IP address via DHCP. 35 | In case of failure (after 60 seconds), the following default parameters will be used: 36 | * MAC address = DE:AD:BE:EF:FE:ED 37 | * IP 192.168.1.241 38 | * Netmask 255.255.255.255 39 | * DNS 192.168.1.1 40 | * Gateway 192.168.1.1 41 | 42 | All those parameters can be altered and saved in EEPROM. 43 | 44 | # References: 45 | * [LDmicro modified for LDuino](https://github.com/f1oat/LDmicro) 46 | * [Official LDmicro software](https://github.com/LDmicro/LDmicro) 47 | * [LDmicro forum](http://cq.cx/ladder-forum.pl) 48 | * [Controllino vendor](http://controllino.biz/) 49 | 50 | # LDuino web interface 51 | 52 | ![LDuino Web interface](/doc/LDuino_web.png) 53 | 54 | # LDmicro example 55 | 56 | ![LDmicro example](/doc/LDmicro.png) 57 | -------------------------------------------------------------------------------- /TinyWebServer.cpp: -------------------------------------------------------------------------------- 1 | // -*- c++ -*- 2 | // 3 | // Copyright 2010 Ovidiu Predescu 4 | // Date: May 2010 5 | // 6 | // Updated: 08-JAN-2012 for Arduno IDE 1.0 by 7 | // Updated: 29-MAR-2013 replacing strtoul with parseHexChar by 8 | // 9 | // TinyWebServer for Arduino. 10 | // 11 | // The DEBUG flag will enable serial console logging in this library 12 | // By default Debugging to the Serl console is OFF. 13 | // This ensures that any scripts using the Serial port are not corrupted 14 | // by the tinywebserver libraries debugging messages. 15 | // 16 | // To ENABLE debugging set the following: 17 | // DEBUG 1 and ENSURE that you have configured the serial port in the 18 | // main Arduino script. 19 | // 20 | // There is an overall size increase of about 340 bytes in code size 21 | // when the debugging is enabled and debugging lines are preceded by 'TWS:' 22 | 23 | 24 | // 10 milliseconds read timeout 25 | #define READ_TIMEOUT 10 26 | 27 | #include "Arduino.h" 28 | 29 | extern "C" { 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | } 36 | 37 | #ifdef CONTROLLINO_MAXI 38 | #include 39 | #else 40 | #include 41 | #endif 42 | 43 | #include 44 | #include 45 | 46 | #include "TinyWebServer.h" 47 | 48 | // Temporary buffer. 49 | static char buffer[160]; 50 | 51 | FLASH_STRING(mime_types, 52 | "HTM*text/html|" 53 | "TXT*text/plain|" 54 | "CSS*text/css|" 55 | "XML*text/xml|" 56 | "JS*text/javascript|" 57 | 58 | "GIF*image/gif|" 59 | "JPG*image/jpeg|" 60 | "PNG*image/png|" 61 | "ICO*image/vnd.microsoft.icon|" 62 | 63 | "MP3*audio/mpeg|" 64 | ); 65 | 66 | // Offset for text/html in `mime_types' above. 67 | static const TinyWebServer::MimeType text_html_content_type = 4; 68 | 69 | TinyWebServer::TinyWebServer(PathHandler handlers[], 70 | const char** headers, 71 | const int port) 72 | : handlers_(handlers), 73 | server_(EthernetServer(port)), 74 | request_type_(UNKNOWN_REQUEST), 75 | client_(EthernetClient(MAX_SOCK_NUM)) 76 | { 77 | if (headers) { 78 | int i = 0; 79 | for (; headers[i] && i < MAX_HEADERS - 1; i++) { 80 | headers_[i].header = headers[i]; 81 | headers_[i].value[0] = 0; 82 | } 83 | headers_[i].header = NULL; 84 | } 85 | } 86 | 87 | void TinyWebServer::begin() { 88 | server_.begin(); 89 | } 90 | 91 | // Process headers. 92 | boolean TinyWebServer::process_headers() 93 | { 94 | 95 | // First clear the header values from the previous HTTP request. 96 | for (int i = 0; headers_[i].header; i++) { 97 | if (headers_[i].value) { 98 | free(headers_[i].value); 99 | headers_[i].value = NULL; 100 | } 101 | } 102 | 103 | enum State { 104 | ERROR, 105 | START_LINE, 106 | HEADER_NAME, 107 | HEADER_VALUE, 108 | HEADER_VALUE_SKIP_INITIAL_SPACES, 109 | HEADER_IGNORE_VALUE, 110 | END_HEADERS, 111 | }; 112 | State state = START_LINE; 113 | 114 | char ch; 115 | int pos; 116 | const char* header; 117 | uint32_t start_time = millis(); 118 | while (1) { 119 | if (should_stop_processing()) { 120 | return false; 121 | } 122 | if (millis() - start_time > READ_TIMEOUT) { 123 | return false; 124 | } 125 | if (!read_next_char(client_, (uint8_t*)&ch)) { 126 | continue; 127 | } 128 | start_time = millis(); 129 | #if DEBUG 130 | Serial.print(ch); 131 | #endif 132 | switch (state) { 133 | case START_LINE: 134 | if (ch == '\r') { 135 | break; 136 | } 137 | else if (ch == '\n') { 138 | state = END_HEADERS; 139 | } 140 | else if (isalnum(ch) || ch == '-') { 141 | pos = 0; 142 | buffer[pos++] = ch; 143 | state = HEADER_NAME; 144 | } 145 | else { 146 | state = ERROR; 147 | } 148 | break; 149 | 150 | case HEADER_NAME: 151 | if (pos + 1 >= sizeof(buffer)) { 152 | state = ERROR; 153 | break; 154 | } 155 | if (ch == ':') { 156 | buffer[pos] = 0; 157 | header = buffer; 158 | if (is_requested_header(&header)) { 159 | state = HEADER_VALUE_SKIP_INITIAL_SPACES; 160 | } 161 | else { 162 | state = HEADER_IGNORE_VALUE; 163 | } 164 | pos = 0; 165 | } 166 | else if (isalnum(ch) || ch == '-') { 167 | buffer[pos++] = ch; 168 | } 169 | else { 170 | state = ERROR; 171 | break; 172 | } 173 | break; 174 | 175 | case HEADER_VALUE_SKIP_INITIAL_SPACES: 176 | if (pos + 1 >= sizeof(buffer)) { 177 | state = ERROR; 178 | break; 179 | } 180 | if (ch != ' ') { 181 | buffer[pos++] = ch; 182 | state = HEADER_VALUE; 183 | } 184 | break; 185 | 186 | case HEADER_VALUE: 187 | if (pos + 1 >= sizeof(buffer)) { 188 | state = ERROR; 189 | break; 190 | } 191 | if (ch == '\n') { 192 | buffer[pos] = 0; 193 | if (!assign_header_value(header, buffer)) { 194 | state = ERROR; 195 | break; 196 | } 197 | state = START_LINE; 198 | } 199 | else { 200 | if (ch != '\r') { 201 | buffer[pos++] = ch; 202 | } 203 | } 204 | break; 205 | 206 | case HEADER_IGNORE_VALUE: 207 | if (ch == '\n') { 208 | state = START_LINE; 209 | } 210 | break; 211 | 212 | default: 213 | break; 214 | } 215 | 216 | if (state == END_HEADERS) { 217 | break; 218 | } 219 | if (state == ERROR) { 220 | return false; 221 | } 222 | } 223 | return true; 224 | } 225 | 226 | void TinyWebServer::process() { 227 | client_ = server_.available(); 228 | if (!client_.connected() || !client_.available()) { 229 | return; 230 | } 231 | 232 | boolean is_complete = get_line(buffer, sizeof(buffer)); 233 | if (!buffer[0]) { 234 | return; 235 | } 236 | #if DEBUG 237 | Serial << F("TWS:New request: "); 238 | Serial.println(buffer); 239 | #endif 240 | if (!is_complete) { 241 | // The requested path is too long. 242 | send_error_code(414); 243 | client_.stop(); 244 | return; 245 | } 246 | 247 | char *saveptr; 248 | char* request_type_str = strtok_r(buffer, " \t", &saveptr); 249 | request_type_ = UNKNOWN_REQUEST; 250 | if (!strcmp("GET", request_type_str)) { 251 | request_type_ = GET; 252 | } 253 | else if (!strcmp("POST", request_type_str)) { 254 | request_type_ = POST; 255 | } 256 | else if (!strcmp("PUT", request_type_str)) { 257 | request_type_ = PUT; 258 | } 259 | else if (!strcmp("DELETE", request_type_str)) { 260 | request_type_ = DELETE; 261 | } 262 | 263 | strncpy(path_, strtok_r(NULL, " \t", &saveptr), sizeof(path_)-1); 264 | 265 | #if DEBUG 266 | Serial << "HTTP " << request_type_str << '[' << request_type_ << ']' << path_ << ":\n"; 267 | #endif 268 | 269 | // Process the headers. 270 | if (!process_headers()) { 271 | // Malformed header line. 272 | send_error_code(417); 273 | client_.stop(); 274 | } 275 | // Header processing finished. Identify the handler to call. 276 | 277 | boolean should_close = true; 278 | boolean found = false; 279 | for (int i = 0; handlers_[i].path; i++) { 280 | int len = strlen(handlers_[i].path); 281 | boolean exact_match = !strcmp(path_, handlers_[i].path); 282 | boolean regex_match = false; 283 | if (handlers_[i].path[len - 1] == '*') { 284 | regex_match = !strncmp(path_, handlers_[i].path, len - 1); 285 | } 286 | if ((exact_match || regex_match) 287 | && (handlers_[i].type == ANY || handlers_[i].type == request_type_)) { 288 | found = true; 289 | should_close = (handlers_[i].handler)(*this); 290 | break; 291 | } 292 | } 293 | 294 | if (!found) { 295 | send_error_code(404); 296 | // (*this) << F("URL not found: "); 297 | // client_->print(path_); 298 | // client_->println(); 299 | } 300 | if (should_close) { 301 | client_.stop(); 302 | } 303 | } 304 | 305 | boolean TinyWebServer::is_requested_header(const char** header) { 306 | if (!headers_) { 307 | return false; 308 | } 309 | for (int i = 0; headers_[i].header; i++) { 310 | if (!strcmp(*header, headers_[i].header)) { 311 | *header = headers_[i].header; 312 | return true; 313 | } 314 | } 315 | return false; 316 | } 317 | 318 | boolean TinyWebServer::assign_header_value(const char* header, char* value) { 319 | boolean found = false; 320 | 321 | for (int i = 0; headers_[i].header; i++) { 322 | // Use pointer equality, since `header' must be the pointer 323 | // inside headers_. 324 | if (header == headers_[i].header) { 325 | headers_[i].value = strdup(value); 326 | found = true; 327 | break; 328 | } 329 | } 330 | return found; 331 | } 332 | 333 | FLASH_STRING(content_type_msg, "Content-Type: "); 334 | 335 | void TinyWebServer::send_error_code(Client& client, int code) { 336 | #if DEBUG 337 | Serial << F("TWS:Returning "); 338 | Serial.println(code, DEC); 339 | #endif 340 | client << F("HTTP/1.1 "); 341 | client.print(code, DEC); 342 | client << F(" OK\r\n"); 343 | if (code != 200) { 344 | end_headers(client); 345 | } 346 | } 347 | 348 | void TinyWebServer::send_content_type(MimeType mime_type) { 349 | client_ << content_type_msg; 350 | 351 | char ch; 352 | int i = mime_type; 353 | while ((ch = mime_types[i++]) != '|') { 354 | client_.print(ch); 355 | } 356 | 357 | client_.println(); 358 | } 359 | 360 | void TinyWebServer::send_content_type(const char* content_type) { 361 | client_ << content_type_msg; 362 | client_.println(content_type); 363 | } 364 | 365 | void TinyWebServer::send_last_modified(const char *date) { 366 | client_ << F("Last-Modified: ") << date << '\n'; 367 | } 368 | 369 | char* TinyWebServer::get_path() { return path_; } 370 | 371 | const TinyWebServer::HttpRequestType TinyWebServer::get_type() { 372 | return request_type_; 373 | } 374 | 375 | const char* TinyWebServer::get_header_value(const char* name) { 376 | if (!headers_) { 377 | return NULL; 378 | } 379 | for (int i = 0; headers_[i].header; i++) { 380 | if (!strcmp(headers_[i].header, name)) { 381 | return headers_[i].value; 382 | } 383 | } 384 | return NULL; 385 | } 386 | 387 | int parseHexChar(char ch) { 388 | if (isdigit(ch)) { 389 | return ch - '0'; 390 | } 391 | ch = tolower(ch); 392 | if (ch >= 'a' && ch <= 'e') { 393 | return ch - 'a' + 10; 394 | } 395 | return 0; 396 | } 397 | 398 | char* TinyWebServer::decode_url_encoded(char* s) { 399 | if (!s) { 400 | return NULL; 401 | } 402 | char* r = s; // Will overwrite input string 403 | char* r2 = r; 404 | char* p = s; 405 | while (*s && (p = strchr(s, '%'))) { 406 | if (p - s) { 407 | memcpy(r2, s, p - s); 408 | r2 += (p - s); 409 | } 410 | // If the remaining number of characters is less than 3, we cannot 411 | // have a complete escape sequence. Break early. 412 | if (strlen(p) < 3) { 413 | // Move the new beginning to the value of p. 414 | s = p; 415 | break; 416 | } 417 | uint8_t r = parseHexChar(*(p + 1)) << 4 | parseHexChar(*(p + 2)); 418 | *r2++ = r; 419 | p += 3; 420 | 421 | // Move the new beginning to the value of p. 422 | s = p; 423 | } 424 | // Copy whatever is left of the string in the result. 425 | int len = strlen(s); 426 | if (len > 0) { 427 | strncpy(r2, s, len); 428 | } 429 | // Append the 0 terminator. 430 | *(r2 + len) = 0; 431 | 432 | return r; 433 | } 434 | 435 | char* TinyWebServer::get_file_from_path(char* path) { 436 | // Obtain the last path component. 437 | char* encoded_fname = strrchr(path, '/'); 438 | if (!encoded_fname) { 439 | return NULL; 440 | } 441 | else { 442 | // Skip past the '/'. 443 | encoded_fname++; 444 | } 445 | char* decoded = decode_url_encoded(encoded_fname); 446 | if (!decoded) { 447 | return NULL; 448 | } 449 | for (char* p = decoded; *p; p++) { 450 | *p = toupper(*p); 451 | } 452 | return decoded; 453 | } 454 | 455 | TinyWebServer::MimeType TinyWebServer::get_mime_type_from_filename( 456 | const char* filename) { 457 | MimeType r = text_html_content_type; 458 | if (!filename) { 459 | return r; 460 | } 461 | 462 | char* ext = strrchr(filename, '.'); 463 | if (ext) { 464 | // We found an extension. Skip past the '.' 465 | ext++; 466 | 467 | char ch; 468 | int i = 0; 469 | while (i < mime_types.length()) { 470 | // Compare the extension. 471 | char* p = ext; 472 | ch = mime_types[i]; 473 | while (*p && ch != '*' && toupper(*p) == ch) { 474 | p++; i++; 475 | ch = mime_types[i]; 476 | } 477 | if (!*p && ch == '*') { 478 | // We reached the end of the extension while checking 479 | // equality with a MIME type: we have a match. Increment i 480 | // to reach past the '*' char, and assign it to `mime_type'. 481 | r = ++i; 482 | break; 483 | } 484 | else { 485 | // Skip past the the '|' character indicating the end of a 486 | // MIME type. 487 | while (mime_types[i++] != '|') 488 | ; 489 | } 490 | } 491 | } 492 | return r; 493 | } 494 | 495 | void TinyWebServer::send_file(SdFile& file) { 496 | size_t size; 497 | while ((size = file.read(buffer, sizeof(buffer))) > 0) { 498 | if (!client_.connected()) { 499 | break; 500 | } 501 | write((uint8_t*)buffer, size); 502 | } 503 | } 504 | 505 | size_t TinyWebServer::write(uint8_t c) { 506 | client_.write(c); 507 | } 508 | 509 | size_t TinyWebServer::write(const char *str) { 510 | client_.write(str); 511 | } 512 | 513 | size_t TinyWebServer::write(const uint8_t *buffer, size_t size) { 514 | client_.write(buffer, size); 515 | } 516 | 517 | boolean TinyWebServer::read_next_char(Client& client, uint8_t* ch) { 518 | if (!client.available()) { 519 | return false; 520 | } 521 | else { 522 | *ch = client.read(); 523 | return true; 524 | } 525 | } 526 | 527 | boolean TinyWebServer::get_line(char* buffer, int size) { 528 | int i = 0; 529 | char ch; 530 | 531 | buffer[0] = 0; 532 | for (; i < size - 1; i++) { 533 | if (!read_next_char(client_, (uint8_t*)&ch)) { 534 | continue; 535 | } 536 | if (ch == '\n') { 537 | break; 538 | } 539 | buffer[i] = ch; 540 | } 541 | buffer[i] = 0; 542 | return i < size - 1; 543 | } 544 | 545 | #if 0 546 | // Returns a newly allocated string containing the field number `which`. 547 | // The first field's index is 0. 548 | // The caller is responsible for freeing the returned value. 549 | char* TinyWebServer::get_field(const char* buffer, int which) { 550 | char* field = NULL; 551 | boolean prev_is_space = false; 552 | int i = 0; 553 | int field_no = 0; 554 | int size = strlen(buffer); 555 | 556 | // Locate the field we need. A field is defined as an area of 557 | // non-space characters delimited by one or more space characters. 558 | for (; field_no < which; field_no++) { 559 | // Skip over space characters 560 | while (i < size && isspace(buffer[i])) { 561 | i++; 562 | } 563 | // Skip over non-space characters. 564 | while (i < size && !isspace(buffer[i])) { 565 | i++; 566 | } 567 | } 568 | 569 | // Now we identify the end of the field that we want. 570 | // Skip over space characters. 571 | while (i < size && isspace(buffer[i])) { 572 | i++; 573 | } 574 | 575 | if (field_no == which && i < size) { 576 | // Now identify where the field ends. 577 | int j = i; 578 | while (j < size && !isspace(buffer[j])) { 579 | j++; 580 | } 581 | 582 | field = (char*)malloc_check(j - i + 1); 583 | if (!field) { 584 | return NULL; 585 | } 586 | memcpy(field, buffer + i, j - i); 587 | field[j - i] = 0; 588 | } 589 | return field; 590 | } 591 | #endif 592 | 593 | // The PUT handler. 594 | 595 | namespace TinyWebPutHandler { 596 | 597 | HandlerFn put_handler_fn = NULL; 598 | 599 | // Fills in `buffer' by reading up to `num_bytes'. 600 | // Returns the number of characters read. 601 | int read_chars(TinyWebServer& web_server, Client& client, 602 | uint8_t* buffer, int size) { 603 | uint8_t ch = 0; 604 | int pos; 605 | for (pos = 0; pos < size && web_server.read_next_char(client, &ch); pos++) { 606 | buffer[pos] = ch; 607 | } 608 | return pos; 609 | } 610 | 611 | boolean put_handler(TinyWebServer& web_server) { 612 | web_server.send_error_code(200); 613 | web_server.end_headers(); 614 | 615 | const char* length_str = web_server.get_header_value("Content-Length"); 616 | long length = atol(length_str); 617 | uint32_t start_time = 0; 618 | boolean watchdog_start = false; 619 | 620 | EthernetClient client = web_server.get_client(); 621 | 622 | if (put_handler_fn) { 623 | (*put_handler_fn)(web_server, START, NULL, length); 624 | } 625 | 626 | uint32_t i; 627 | for (i = 0; i < length && client.connected();) { 628 | int16_t size = read_chars(web_server, client, (uint8_t*)buffer, 64); 629 | if (!size) { 630 | if (watchdog_start) { 631 | if (millis() - start_time > 30000) { 632 | // Exit if there has been zero data from connected client 633 | // for more than 30 seconds. 634 | #if DEBUG 635 | Serial << F("TWS:There has been no data for >30 Sec.\n"); 636 | #endif 637 | break; 638 | } 639 | } 640 | else { 641 | // We have hit an empty buffer, start the watchdog. 642 | start_time = millis(); 643 | watchdog_start = true; 644 | } 645 | continue; 646 | } 647 | i += size; 648 | // Ensure we re-start the watchdog if we get ANY data input. 649 | watchdog_start = false; 650 | 651 | if (put_handler_fn) { 652 | (*put_handler_fn)(web_server, WRITE, buffer, size); 653 | } 654 | } 655 | if (put_handler_fn) { 656 | (*put_handler_fn)(web_server, END, NULL, 0); 657 | } 658 | 659 | return true; 660 | } 661 | 662 | }; 663 | -------------------------------------------------------------------------------- /TinyWebServer.h: -------------------------------------------------------------------------------- 1 | // -*- c++ -*- 2 | // 3 | // Copyright 2010 Ovidiu Predescu 4 | // Date: May, June 2010 5 | // 6 | // Updated: 08-JAN-2012 for Arduno IDE 1.0 by 7 | // 8 | // TinyWebServer for Arduino. 9 | 10 | #ifndef __WEB_SERVER_H__ 11 | #define __WEB_SERVER_H__ 12 | 13 | #include 14 | 15 | class SdFile; 16 | class TinyWebServer; 17 | 18 | namespace TinyWebPutHandler { 19 | enum PutAction { 20 | START, 21 | WRITE, 22 | END 23 | }; 24 | 25 | typedef void (*HandlerFn)(TinyWebServer& web_server, 26 | PutAction action, 27 | char* buffer, int size); 28 | 29 | // An HTTP handler that knows how to handle file uploads using the 30 | // PUT method. Set the `put_handler_fn' variable below to your own 31 | // function to handle the characters of the uploaded function. 32 | boolean put_handler(TinyWebServer& web_server); 33 | extern HandlerFn put_handler_fn; 34 | }; 35 | 36 | class TinyWebServer : public Print { 37 | public: 38 | // An HTTP path handler. The handler function takes the path it 39 | // registered for as argument, and the Client object to handle the 40 | // response. 41 | // 42 | // The function should return true if it finished handling the request 43 | // and the connection should be closed. 44 | typedef boolean (*WebHandlerFn)(TinyWebServer& web_server); 45 | 46 | enum HttpRequestType { 47 | UNKNOWN_REQUEST, 48 | GET, 49 | HEAD, 50 | POST, 51 | PUT, 52 | DELETE, 53 | ANY, 54 | }; 55 | 56 | // An identifier for a MIME type. The number is opaque to a human, 57 | // but it's really an offset in the `mime_types' array. 58 | typedef uint16_t MimeType; 59 | 60 | typedef struct { 61 | const char* path; 62 | HttpRequestType type; 63 | WebHandlerFn handler; 64 | } PathHandler; 65 | 66 | // Initialize the web server using a NULL terminated array of path 67 | // handlers, and a NULL terminated array of headers the handlers are 68 | // interested in. 69 | // 70 | // NOTE: Make sure the header names are all lowercase. 71 | TinyWebServer(PathHandler handlers[], const char** headers, 72 | const int port=80); 73 | 74 | // Call this method to start the HTTP server 75 | void begin(); 76 | 77 | // Handles a possible HTTP request. It will return immediately if no 78 | // client has connected. Otherwise the request is handled 79 | // synchronously. 80 | // 81 | // Call this method from the main loop() function to have the Web 82 | // server handle incoming requests. 83 | void process(); 84 | 85 | // Sends the HTTP status code to the connect HTTP client. 86 | void send_error_code(int code) { 87 | send_error_code(client_, code); 88 | } 89 | static void send_error_code(Client& client, int code); 90 | 91 | void send_content_type(MimeType mime_type); 92 | void send_content_type(const char* content_type); 93 | void send_last_modified(const char *date); 94 | 95 | // Call this method to indicate the end of the headers. 96 | inline void end_headers() { client_.println(); } 97 | static inline void end_headers(Client& client) { client.println(); } 98 | 99 | // void send_error_code(MimeType mime_type, int code); 100 | // void send_error_code(const char* content_type, int code); 101 | 102 | char* get_path(); 103 | const HttpRequestType get_type(); 104 | const char* get_header_value(const char* header); 105 | EthernetClient& get_client() { return client_; } 106 | 107 | // Processes the HTTP headers and assigns values to the requested 108 | // ones in headers_. Returns true when successful, false in case of 109 | // errors. 110 | boolean process_headers(); 111 | 112 | // Helper methods 113 | 114 | // Assumes `s' is an HTTP encoded URL, replaces all the escape 115 | // characters in it and returns the unencoded version. For example 116 | // for "/index%2Ehtm", this method returns "index.htm". 117 | // 118 | static char* decode_url_encoded(char* s); 119 | 120 | // Assumes the last component of the URL path is a file 121 | // name. Returns the file name in upper case, ready to passed to 122 | // SdFile's open() method. 123 | // 124 | // In addition to the file name, it sets `mime_type' to an identifier 125 | // 126 | static char* get_file_from_path(char* path); 127 | 128 | // Guesses a MIME type based on the extension of `filename'. If none 129 | // could be guessed, the equivalent of text/html is returned. 130 | static MimeType get_mime_type_from_filename(const char* filename); 131 | 132 | // Sends the contents of `file' to the currently connected 133 | // client. The file must be opened in read mode. 134 | // 135 | // This is mainly an optimization to reuse the internal static 136 | // buffer used by this class, which saves us some RAM. 137 | void send_file(SdFile& file); 138 | 139 | // These methods write directly in the response stream of the 140 | // connected client 141 | virtual size_t write(uint8_t c); 142 | virtual size_t write(const char *str); 143 | virtual size_t write(const uint8_t *buffer, size_t size); 144 | 145 | // Some methods used for testing purposes 146 | 147 | // Returns true if the HTTP request processing should be stopped. 148 | virtual boolean should_stop_processing() { return !client_.connected();} 149 | 150 | // Reads a character from the request's input stream. Returns true 151 | // if the character could be read, false otherwise. 152 | virtual boolean read_next_char(Client& client, uint8_t* ch); 153 | 154 | protected: 155 | // Returns the field number `which' from buffer. Fields are 156 | // separated by spaces. Should be a private method, but made public 157 | // so it can be tested. 158 | static char* get_field(const char* buffer, int which); 159 | 160 | private: 161 | // The path handlers 162 | PathHandler* handlers_; 163 | 164 | typedef struct { 165 | const char* header; 166 | char *value; 167 | } HeaderValue; 168 | 169 | // The headers 170 | static const int MAX_HEADERS = 8; 171 | HeaderValue headers_[MAX_HEADERS]; 172 | 173 | // The TCP/IP server we use. 174 | EthernetServer server_; 175 | 176 | char path_[32]; 177 | HttpRequestType request_type_; 178 | EthernetClient client_; 179 | 180 | // Reads a line from the HTTP request sent by an HTTP client. The 181 | // line is put in `buffer' and up to `size' characters are written 182 | // in it. 183 | boolean get_line(char* buffer, int size); 184 | 185 | // Returns true if the header is marked as requested in the headers_ 186 | // array. As a side effect, the pointer to the actual header is made 187 | // to point to the one in the headers_ array. 188 | boolean is_requested_header(const char** header); 189 | 190 | boolean assign_header_value(const char* header, char* value); 191 | }; 192 | 193 | #endif /* __WEB_SERVER_H__ */ 194 | -------------------------------------------------------------------------------- /__vm/.LDuino.vsarduino.h: -------------------------------------------------------------------------------- 1 | /* 2 | Editor: http://www.visualmicro.com 3 | visual micro and the arduino ide ignore this code during compilation. this code is automatically maintained by visualmicro, manual changes to this file will be overwritten 4 | the contents of the Visual Micro sketch sub folder can be deleted prior to publishing a project 5 | all non-arduino files created by visual micro and all visual studio project or solution files can be freely deleted and are not required to compile a sketch (do not delete your own code!). 6 | note: debugger breakpoints are stored in '.sln' or '.asln' files, knowledge of last uploaded breakpoints is stored in the upload.vmps.xml file. Both files are required to continue a previous debug session without needing to compile and upload again 7 | 8 | Hardware: Controllino MAXI, Platform=avr, Package=ControllinoHardware-master 9 | */ 10 | 11 | #if defined(_VMICRO_INTELLISENSE) 12 | 13 | #ifndef _VSARDUINO_H_ 14 | #define _VSARDUINO_H_ 15 | #define __AVR_ATmega2560__ 16 | #define DEBUG 0 17 | #define F_CPU 16000000L 18 | #define ARDUINO 10609 19 | #define ARDUINO_AVR_MEGA2560 20 | #define ARDUINO_ARCH_AVR 21 | #define __cplusplus 201103L 22 | #define __AVR__ 23 | #define __inline__ 24 | #define __asm__(...) 25 | #define __extension__ 26 | #define __inline__ 27 | #define __volatile__ 28 | #define GCC_VERSION 40902 29 | 30 | #define __cplusplus 201103L 31 | #undef __cplusplus 32 | #define __cplusplus 201103L 33 | 34 | #define volatile(va_arg) 35 | #define _CONST 36 | #define __builtin_va_start 37 | #define __builtin_va_end 38 | #define __attribute__(...) 39 | #define NOINLINE __attribute__((noinline)) 40 | #define prog_void 41 | #define PGM_VOID_P int 42 | 43 | 44 | #ifndef __builtin_constant_p 45 | #define __builtin_constant_p __attribute__((__const__)) 46 | #endif 47 | #ifndef __builtin_strlen 48 | #define __builtin_strlen __attribute__((__const__)) 49 | #endif 50 | 51 | 52 | #define NEW_H 53 | typedef void *__builtin_va_list; 54 | //extern "C" void __cxa_pure_virtual() {;} 55 | 56 | typedef int div_t; 57 | typedef int ldiv_t; 58 | 59 | 60 | typedef void *__builtin_va_list; 61 | //extern "C" void __cxa_pure_virtual() {;} 62 | 63 | 64 | 65 | #include 66 | #include 67 | //#undef F 68 | //#define F(string_literal) ((const PROGMEM char *)(string_literal)) 69 | #undef PSTR 70 | #define PSTR(string_literal) ((const PROGMEM char *)(string_literal)) 71 | 72 | typedef unsigned char uint8_t; 73 | 74 | #define pgm_read_byte(address_short) uint8_t() 75 | #define pgm_read_word(address_short) uint16_t() 76 | #define pgm_read_dword(address_short) uint32_t() 77 | #define pgm_read_float(address_short) float() 78 | #define pgm_read_ptr(address_short) short() 79 | 80 | #include "LDuino.ino" 81 | #include "IO.ino" 82 | #endif 83 | #endif 84 | -------------------------------------------------------------------------------- /doc/LDmicro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1oat/LDuino/d054cdcccded12e4b40e457f0288768c55a742d7/doc/LDmicro.png -------------------------------------------------------------------------------- /doc/LDuino_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1oat/LDuino/d054cdcccded12e4b40e457f0288768c55a742d7/doc/LDuino_web.png -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | inMB KEYWORD2 2 | inNotMB KEYWORD2 3 | outMB KEYWORD2 4 | outNotMB KEYWORD2 5 | -------------------------------------------------------------------------------- /ldmicro/LDuino-valid.ld: -------------------------------------------------------------------------------- 1 | LDmicro0.1 2 | MICRO=Controllino Maxi / Ext bytecode 3 | CYCLE=10000 us at Timer1, YPlcCycleDuty:0 4 | CRYSTAL=4000000 Hz 5 | BAUD=2400 Hz 6 | 7 | IO LIST 8 | XA1 at 96 0 0 9 | YD1 at 7 0 0 10 | YD2 at 1 0 0 11 | YD3 at 5 0 0 12 | AA0 at 97 0 0 13 | END 14 | 15 | PROGRAM 16 | RUNG 17 | PARALLEL 18 | SERIES 19 | CONTACTS XA1 0 0 20 | PARALLEL 21 | MOVE Pduty 50 22 | SERIES 23 | TON Tdelay 500000 24 | COIL YD3 0 0 0 0 25 | END 26 | END 27 | END 28 | SERIES 29 | CONTACTS XA1 1 0 30 | MOVE Pduty 75 31 | END 32 | END 33 | END 34 | RUNG 35 | SET_PWM Pduty 1000 P0 36 | END 37 | RUNG 38 | READ_ADC AA0 39 | END 40 | RUNG 41 | DIV Padc AA0 4 42 | END 43 | RUNG 44 | SET_PWM Padc 1000 P1 45 | END 46 | RUNG 47 | CONTACTS Rblink 1 0 48 | TON Ton 500000 49 | TOF Toff 150000 50 | PARALLEL 51 | COIL Rblink 0 0 0 0 52 | COIL Mmb0 0 0 0 0 53 | COIL YD1 0 0 0 0 54 | SERIES 55 | OSR 56 | ADD Hmb3 Hmb3 1 57 | END 58 | END 59 | END 60 | RUNG 61 | CONTACTS Imb0 0 0 62 | PARALLEL 63 | COIL YD2 0 0 0 0 64 | COIL Mmb1 0 0 0 0 65 | END 66 | END 67 | RUNG 68 | SET_PWM Pblink 1000 P2 69 | END 70 | RUNG 71 | CONTACTS Rclock 0 0 72 | PARALLEL 73 | TON Tnew 20000 74 | SERIES 75 | CTC Ccnt 50 0 76 | OPEN 77 | END 78 | END 79 | PARALLEL 80 | COIL Rclock 1 0 0 0 81 | MUL Pblink Ccnt 5 82 | END 83 | END 84 | -------------------------------------------------------------------------------- /ldmicro/LDuino-valid.pl: -------------------------------------------------------------------------------- 1 | 0:# 2 | 1:# ======= START RUNG 1 ======= 3 | 2:set bit '$rung_top' 4 | 4:# start series [ 5 | 5:# start parallel [ 6 | 6:let bit '$parThis_0000' := '$rung_top' 7 | 7:# start series [ 8 | 8:# ELEM_CONTACTS 9 | 9:if not 'XA1' { 10 | 10: clear bit '$parThis_0000' 11 | 11:} 12 | 13:# start parallel [ 13 | 14:# ELEM_MOVE 14 | 15:if '$parThis_0000' { 15 | 16: let var 'Pduty' := 50 16 | 17:} 17 | 19:let bit '$parThis_0001' := '$parThis_0000' 18 | 20:# start series [ 19 | 21:# ELEM_TON 20 | 22:if '$parThis_0001' { 21 | 23: if 'Tdelay' < 50 { 22 | 24: increment 'Tdelay' 23 | 25: clear bit '$parThis_0001' 24 | 26: } 25 | 27:} else { 26 | 28: let var 'Tdelay' := 0 27 | 29:} 28 | 31:# ELEM_COIL 29 | 32:let bit 'YD3' := '$parThis_0001' 30 | 34:# ] finish series 31 | 35:# ] finish parallel 32 | 36:# ] finish series 33 | 37:let bit '$parThis_0000' := '$rung_top' 34 | 38:# start series [ 35 | 39:# ELEM_CONTACTS 36 | 40:if 'XA1' { 37 | 41: clear bit '$parThis_0000' 38 | 42:} 39 | 44:# ELEM_MOVE 40 | 45:if '$parThis_0000' { 41 | 46: let var 'Pduty' := 75 42 | 47:} 43 | 49:# ] finish series 44 | 50:# ] finish parallel 45 | 51:# ] finish series 46 | 52:# 47 | 53:# ======= START RUNG 2 ======= 48 | 54:set bit '$rung_top' 49 | 56:# start series [ 50 | 57:# ELEM_SET_PWM 51 | 58:if '$rung_top' { 52 | 59: set pwm 'Pduty' 1000 Hz out P0 53 | 60:} 54 | 62:# ] finish series 55 | 63:# 56 | 64:# ======= START RUNG 3 ======= 57 | 65:set bit '$rung_top' 58 | 67:# start series [ 59 | 68:# ELEM_READ_ADC 60 | 69:if '$rung_top' { 61 | 70: read adc 'AA0' 62 | 71:} 63 | 73:# ] finish series 64 | 74:# 65 | 75:# ======= START RUNG 4 ======= 66 | 76:set bit '$rung_top' 67 | 78:# start series [ 68 | 79:# ELEM_DIV 69 | 80:if '$rung_top' { 70 | 81: let var '$scratch2' := 4 71 | 82: let var 'Padc' := 'AA0' / '$scratch2' 72 | 83:} 73 | 85:# ] finish series 74 | 86:# 75 | 87:# ======= START RUNG 5 ======= 76 | 88:set bit '$rung_top' 77 | 90:# start series [ 78 | 91:# ELEM_SET_PWM 79 | 92:if '$rung_top' { 80 | 93: set pwm 'Padc' 1000 Hz out P1 81 | 94:} 82 | 96:# ] finish series 83 | 97:# 84 | 98:# ======= START RUNG 6 ======= 85 | 99:set bit '$rung_top' 86 | 101:# start series [ 87 | 102:# ELEM_CONTACTS 88 | 103:if 'Rblink' { 89 | 104: clear bit '$rung_top' 90 | 105:} 91 | 107:# ELEM_TON 92 | 108:if '$rung_top' { 93 | 109: if 'Ton' < 50 { 94 | 110: increment 'Ton' 95 | 111: clear bit '$rung_top' 96 | 112: } 97 | 113:} else { 98 | 114: let var 'Ton' := 0 99 | 115:} 100 | 117:# ELEM_TOF 101 | 118:if not '$Toff_antiglitch' { 102 | 119: let var 'Toff' := 15 103 | 120:} 104 | 121:set bit '$Toff_antiglitch' 105 | 122:if not '$rung_top' { 106 | 123: if 'Toff' < 15 { 107 | 124: increment 'Toff' 108 | 125: set bit '$rung_top' 109 | 126: } 110 | 127:} else { 111 | 128: let var 'Toff' := 0 112 | 129:} 113 | 131:# start parallel [ 114 | 132:let bit '$parThis_0002' := '$rung_top' 115 | 133:# ELEM_COIL 116 | 134:let bit 'Rblink' := '$parThis_0002' 117 | 136:let bit '$parThis_0002' := '$rung_top' 118 | 137:# ELEM_COIL 119 | 138:let bit 'Mmb0' := '$parThis_0002' 120 | 140:let bit '$parThis_0002' := '$rung_top' 121 | 141:# ELEM_COIL 122 | 142:let bit 'YD1' := '$parThis_0002' 123 | 144:let bit '$parThis_0002' := '$rung_top' 124 | 145:# start series [ 125 | 146:# ELEM_ONE_SHOT_RISING 126 | 147:if '$parThis_0002' { 127 | 148: if '$oneShot_0000_ONE_SHOT_RISING_' { 128 | 149: clear bit '$parThis_0002' 129 | 150: } else { 130 | 151: set bit '$oneShot_0000_ONE_SHOT_RISING_' 131 | 152: } 132 | 153:} else { 133 | 154: clear bit '$oneShot_0000_ONE_SHOT_RISING_' 134 | 155:} 135 | 157:# ELEM_ADD 136 | 158:if '$parThis_0002' { 137 | 159: increment 'Hmb3' 138 | 160:} 139 | 162:# ] finish series 140 | 163:# ] finish parallel 141 | 164:# ] finish series 142 | 165:# 143 | 166:# ======= START RUNG 7 ======= 144 | 167:set bit '$rung_top' 145 | 169:# start series [ 146 | 170:# ELEM_CONTACTS 147 | 171:if not 'Imb0' { 148 | 172: clear bit '$rung_top' 149 | 173:} 150 | 175:# start parallel [ 151 | 176:let bit '$parThis_0003' := '$rung_top' 152 | 177:# ELEM_COIL 153 | 178:let bit 'YD2' := '$parThis_0003' 154 | 180:let bit '$parThis_0003' := '$rung_top' 155 | 181:# ELEM_COIL 156 | 182:let bit 'Mmb1' := '$parThis_0003' 157 | 184:# ] finish parallel 158 | 185:# ] finish series 159 | 186:# 160 | 187:# ======= START RUNG 8 ======= 161 | 188:set bit '$rung_top' 162 | 190:# start series [ 163 | 191:# ELEM_SET_PWM 164 | 192:if '$rung_top' { 165 | 193: set pwm 'Pblink' 1000 Hz out P2 166 | 194:} 167 | 196:# ] finish series 168 | 197:# 169 | 198:# ======= START RUNG 9 ======= 170 | 199:set bit '$rung_top' 171 | 201:# start series [ 172 | 202:# ELEM_CONTACTS 173 | 203:if not 'Rclock' { 174 | 204: clear bit '$rung_top' 175 | 205:} 176 | 207:# start parallel [ 177 | 208:clear bit '$parOut_0000' 178 | 209:let bit '$parThis_0004' := '$rung_top' 179 | 210:# ELEM_TON 180 | 211:if '$parThis_0004' { 181 | 212: if 'Tnew' < 2 { 182 | 213: increment 'Tnew' 183 | 214: clear bit '$parThis_0004' 184 | 215: } 185 | 216:} else { 186 | 217: let var 'Tnew' := 0 187 | 218:} 188 | 220:if '$parThis_0004' { 189 | 221: set bit '$parOut_0000' 190 | 222:} 191 | 223:let bit '$parThis_0004' := '$rung_top' 192 | 224:# start series [ 193 | 225:# ELEM_CTC 194 | 226:if '$parThis_0004' { 195 | 227: clear bit '$parThis_0004' 196 | 228: if not '$oneShot_0001_CTC_Ccnt' { 197 | 229: set bit '$oneShot_0001_CTC_Ccnt' 198 | 230: increment 'Ccnt' 199 | 231: if 'Ccnt' < 51 { 200 | 232: } else { 201 | 233: let var 'Ccnt' := 0 202 | 234: set bit '$parThis_0004' 203 | 235: } 204 | 236: } 205 | 237:} else { 206 | 238: clear bit '$oneShot_0001_CTC_Ccnt' 207 | 239:} 208 | 241:# ELEM_OPEN 209 | 242:clear bit '$parThis_0004' 210 | 244:# ] finish series 211 | 245:if '$parThis_0004' { 212 | 246: set bit '$parOut_0000' 213 | 247:} 214 | 248:let bit '$rung_top' := '$parOut_0000' 215 | 249:# ] finish parallel 216 | 250:# start parallel [ 217 | 251:let bit '$parThis_0005' := '$rung_top' 218 | 252:# ELEM_COIL 219 | 253:if '$parThis_0005' { 220 | 254: clear bit 'Rclock' 221 | 255:} else { 222 | 256: set bit 'Rclock' 223 | 257:} 224 | 259:# ELEM_MUL 225 | 260:if '$rung_top' { 226 | 261: let var '$scratch2' := 5 227 | 262: let var 'Pblink' := 'Ccnt' * '$scratch2' 228 | 263:} 229 | 265:# ] finish parallel 230 | 266:# ] finish series 231 | -------------------------------------------------------------------------------- /ldmicro/LDuino-valid.xint: -------------------------------------------------------------------------------- 1 | $$IO 23 35 2 | 0 Ccnt 1 0 0 00000 3 | 1 Padc 1 0 0 00000 4 | 2 Pblink 1 0 0 00000 5 | 3 Pduty 1 0 0 00000 6 | 4 Ccnt 5 0 0 00000 7 | 5 XA1 7 55 0 00000 8 | 6 YD1 8 3 0 00000 9 | 7 YD2 8 4 0 00000 10 | 8 YD3 8 5 0 00000 11 | 9 AA0 9 54 0 00000 12 | 10 P0 12 2 0 00000 13 | 11 P1 12 6 0 00000 14 | 12 P2 12 7 0 00000 15 | 13 Rblink 13 0 0 00000 16 | 14 Rclock 13 0 0 00000 17 | 15 Tdelay 16 0 0 00000 18 | 16 Tnew 16 0 0 00000 19 | 17 Ton 16 0 0 00000 20 | 18 Toff 17 0 0 00000 21 | 19 Imb0 18 0 0 00000 22 | 20 Mmb0 19 0 0 00000 23 | 21 Mmb1 19 0 0 00000 24 | 22 Hmb3 20 0 0 00000 25 | $$LDcode 320 26 | 01170318173305020218321804040332 27 | 0003191832190B340F320004060F0219 28 | A004040F000003081903181732050202 29 | 1832180404034B0001173217050C03E8 30 | 030A01173217020B090117321708041A 31 | 04000A01091A01173217050C01E8030B 32 | 0117320D02021732170B341132000406 33 | 110217A00404110000331B0404120F00 34 | 011B33170B34120F000406120117A004 35 | 04120000031C17030D1C031C1703141C 36 | 031C1703061C031C17321C0B321D0402 37 | 1CA002011DA002021D321C0206160117 38 | 3313020217031E1703071E031E170315 39 | 1E01173217050C02E8030C0117330E02 40 | 0217021F03201732200B341002000406 41 | 100220A00404100000322002011F0320 42 | 17322018022033211101210600340033 43 | 0002A006040000000120A00202210220 44 | 322002011F03171F032217322204020E 45 | A002010E321708041A05000902001AFF 46 | $$cycle 10000 us 47 | -------------------------------------------------------------------------------- /ldmicro/LDuino-validv2.ld: -------------------------------------------------------------------------------- 1 | LDmicro0.2 2 | MICRO=Controllino Maxi / Ext bytecode 3 | CYCLE=10000 us at Timer1, YPlcCycleDuty:0 4 | CRYSTAL=4000000 Hz 5 | BAUD=2400 Hz 6 | 7 | VAR LIST 8 | 2 bytes Ccnt Now not used !!! 9 | 2 bytes Hmb3 Now not used !!! 10 | 2 bytes Imb0 Now not used !!! 11 | 2 bytes Mmb0 Now not used !!! 12 | 2 bytes Mmb1 Now not used !!! 13 | 2 bytes Padc Now not used !!! 14 | 2 bytes Pblink Now not used !!! 15 | 2 bytes Pduty Now not used !!! 16 | 2 bytes Rblink Now not used !!! 17 | 2 bytes Rclock Now not used !!! 18 | 2 bytes Tdelay Now not used !!! 19 | 2 bytes Tnew Now not used !!! 20 | 2 bytes Toff Now not used !!! 21 | 2 bytes Ton Now not used !!! 22 | END 23 | 24 | IO LIST 25 | XA1 at 96 0 0 26 | YD1 at 7 0 0 27 | YD2 at 1 0 0 28 | YD3 at 5 0 0 29 | AA0 at 97 0 0 30 | P0 at 6 0 0 31 | P1 at 15 0 0 32 | P2 at 16 0 0 33 | Imb0 at 0 1 0 34 | Mmb0 at 0 1 0 35 | Mmb1 at 0 1 1 36 | Hmb3 at 0 1 0 37 | END 38 | 39 | PROGRAM 40 | RUNG 1 41 | PARALLEL 42 | SERIES 43 | CONTACTS XA1 0 0 44 | PARALLEL 45 | MOVE Pduty 50 46 | SERIES 47 | TON Tdelay 500000 48 | COIL YD3 0 0 0 0 49 | END 50 | END 51 | END 52 | SERIES 53 | CONTACTS XA1 1 0 54 | MOVE Pduty 75 55 | END 56 | END 57 | END 58 | RUNG 2 59 | SET_PWM Pduty 1000 P0 60 | END 61 | RUNG 3 62 | READ_ADC AA0 63 | END 64 | RUNG 4 65 | DIV Padc AA0 4 66 | END 67 | RUNG 5 68 | SET_PWM Padc 1000 P1 69 | END 70 | RUNG 6 71 | CONTACTS Rblink 1 0 72 | TON Ton 500000 73 | TOF Toff 150000 74 | PARALLEL 75 | COIL Rblink 0 0 0 0 76 | COIL Mmb0 0 0 0 0 77 | COIL YD1 0 0 0 0 78 | SERIES 79 | OSR 80 | ADD Hmb3 Hmb3 1 81 | END 82 | END 83 | END 84 | RUNG 7 85 | CONTACTS Imb0 0 0 86 | PARALLEL 87 | COIL YD2 0 0 0 0 88 | COIL Mmb1 0 0 0 0 89 | END 90 | END 91 | RUNG 8 92 | SET_PWM Pblink 1000 P2 93 | END 94 | RUNG 9 95 | CONTACTS Rclock 0 0 96 | PARALLEL 97 | TON Tnew 20000 98 | SERIES 99 | CTC Ccnt 50 0 100 | OPEN 101 | END 102 | END 103 | PARALLEL 104 | COIL Rclock 1 0 0 0 105 | MUL Pblink Ccnt 5 106 | END 107 | END 108 | -------------------------------------------------------------------------------- /ldmicro/LDuino-validv2.pl: -------------------------------------------------------------------------------- 1 | 0:# 2 | 1:# ======= START RUNG 1 ======= 3 | 2:set bit '$rung_top' 4 | 4:# start series [ 5 | 5:# start parallel [ 6 | 6:let bit '$parThis_0000' := '$rung_top' 7 | 7:# start series [ 8 | 8:# ELEM_CONTACTS 9 | 9:if not 'XA1' { 10 | 10: clear bit '$parThis_0000' 11 | 11:} 12 | 13:# start parallel [ 13 | 14:# ELEM_MOVE 14 | 15:if '$parThis_0000' { 15 | 16: let var 'Pduty' := 50 16 | 17:} 17 | 19:let bit '$parThis_0001' := '$parThis_0000' 18 | 20:# start series [ 19 | 21:# ELEM_TON 20 | 22:if '$parThis_0001' { 21 | 23: if 'Tdelay' < 50 { 22 | 24: increment 'Tdelay' 23 | 25: clear bit '$parThis_0001' 24 | 26: } 25 | 27:} else { 26 | 28: let var 'Tdelay' := 0 27 | 29:} 28 | 31:# ELEM_COIL 29 | 32:let bit 'YD3' := '$parThis_0001' 30 | 34:# ] finish series 31 | 35:# ] finish parallel 32 | 36:# ] finish series 33 | 37:let bit '$parThis_0000' := '$rung_top' 34 | 38:# start series [ 35 | 39:# ELEM_CONTACTS 36 | 40:if 'XA1' { 37 | 41: clear bit '$parThis_0000' 38 | 42:} 39 | 44:# ELEM_MOVE 40 | 45:if '$parThis_0000' { 41 | 46: let var 'Pduty' := 75 42 | 47:} 43 | 49:# ] finish series 44 | 50:# ] finish parallel 45 | 51:# ] finish series 46 | 52:# 47 | 53:# ======= START RUNG 2 ======= 48 | 54:set bit '$rung_top' 49 | 56:# start series [ 50 | 57:# ELEM_SET_PWM 51 | 58:if '$rung_top' { 52 | 59: set pwm 'Pduty' 1000 Hz out P0 53 | 60:} 54 | 62:# ] finish series 55 | 63:# 56 | 64:# ======= START RUNG 3 ======= 57 | 65:set bit '$rung_top' 58 | 67:# start series [ 59 | 68:# ELEM_READ_ADC 60 | 69:if '$rung_top' { 61 | 70: read adc 'AA0' 62 | 71:} 63 | 73:# ] finish series 64 | 74:# 65 | 75:# ======= START RUNG 4 ======= 66 | 76:set bit '$rung_top' 67 | 78:# start series [ 68 | 79:# ELEM_DIV 69 | 80:if '$rung_top' { 70 | 81: let var '$scratch2' := 4 71 | 82: let var 'Padc' := 'AA0' / '$scratch2' 72 | 83:} 73 | 85:# ] finish series 74 | 86:# 75 | 87:# ======= START RUNG 5 ======= 76 | 88:set bit '$rung_top' 77 | 90:# start series [ 78 | 91:# ELEM_SET_PWM 79 | 92:if '$rung_top' { 80 | 93: set pwm 'Padc' 1000 Hz out P1 81 | 94:} 82 | 96:# ] finish series 83 | 97:# 84 | 98:# ======= START RUNG 6 ======= 85 | 99:set bit '$rung_top' 86 | 101:# start series [ 87 | 102:# ELEM_CONTACTS 88 | 103:if 'Rblink' { 89 | 104: clear bit '$rung_top' 90 | 105:} 91 | 107:# ELEM_TON 92 | 108:if '$rung_top' { 93 | 109: if 'Ton' < 50 { 94 | 110: increment 'Ton' 95 | 111: clear bit '$rung_top' 96 | 112: } 97 | 113:} else { 98 | 114: let var 'Ton' := 0 99 | 115:} 100 | 117:# ELEM_TOF 101 | 118:if not '$Toff_antiglitch' { 102 | 119: let var 'Toff' := 15 103 | 120:} 104 | 121:set bit '$Toff_antiglitch' 105 | 122:if not '$rung_top' { 106 | 123: if 'Toff' < 15 { 107 | 124: increment 'Toff' 108 | 125: set bit '$rung_top' 109 | 126: } 110 | 127:} else { 111 | 128: let var 'Toff' := 0 112 | 129:} 113 | 131:# start parallel [ 114 | 132:let bit '$parThis_0002' := '$rung_top' 115 | 133:# ELEM_COIL 116 | 134:let bit 'Rblink' := '$parThis_0002' 117 | 136:let bit '$parThis_0002' := '$rung_top' 118 | 137:# ELEM_COIL 119 | 138:let bit 'Mmb0' := '$parThis_0002' 120 | 140:let bit '$parThis_0002' := '$rung_top' 121 | 141:# ELEM_COIL 122 | 142:let bit 'YD1' := '$parThis_0002' 123 | 144:let bit '$parThis_0002' := '$rung_top' 124 | 145:# start series [ 125 | 146:# ELEM_ONE_SHOT_RISING 126 | 147:if '$parThis_0002' { 127 | 148: if '$oneShot_0000_ONE_SHOT_RISING_' { 128 | 149: clear bit '$parThis_0002' 129 | 150: } else { 130 | 151: set bit '$oneShot_0000_ONE_SHOT_RISING_' 131 | 152: } 132 | 153:} else { 133 | 154: clear bit '$oneShot_0000_ONE_SHOT_RISING_' 134 | 155:} 135 | 157:# ELEM_ADD 136 | 158:if '$parThis_0002' { 137 | 159: increment 'Hmb3' 138 | 160:} 139 | 162:# ] finish series 140 | 163:# ] finish parallel 141 | 164:# ] finish series 142 | 165:# 143 | 166:# ======= START RUNG 7 ======= 144 | 167:set bit '$rung_top' 145 | 169:# start series [ 146 | 170:# ELEM_CONTACTS 147 | 171:if not 'Imb0' { 148 | 172: clear bit '$rung_top' 149 | 173:} 150 | 175:# start parallel [ 151 | 176:let bit '$parThis_0003' := '$rung_top' 152 | 177:# ELEM_COIL 153 | 178:let bit 'YD2' := '$parThis_0003' 154 | 180:let bit '$parThis_0003' := '$rung_top' 155 | 181:# ELEM_COIL 156 | 182:let bit 'Mmb1' := '$parThis_0003' 157 | 184:# ] finish parallel 158 | 185:# ] finish series 159 | 186:# 160 | 187:# ======= START RUNG 8 ======= 161 | 188:set bit '$rung_top' 162 | 190:# start series [ 163 | 191:# ELEM_SET_PWM 164 | 192:if '$rung_top' { 165 | 193: set pwm 'Pblink' 1000 Hz out P2 166 | 194:} 167 | 196:# ] finish series 168 | 197:# 169 | 198:# ======= START RUNG 9 ======= 170 | 199:set bit '$rung_top' 171 | 201:# start series [ 172 | 202:# ELEM_CONTACTS 173 | 203:if not 'Rclock' { 174 | 204: clear bit '$rung_top' 175 | 205:} 176 | 207:# start parallel [ 177 | 208:clear bit '$parOut_0000' 178 | 209:let bit '$parThis_0004' := '$rung_top' 179 | 210:# ELEM_TON 180 | 211:if '$parThis_0004' { 181 | 212: if 'Tnew' < 2 { 182 | 213: increment 'Tnew' 183 | 214: clear bit '$parThis_0004' 184 | 215: } 185 | 216:} else { 186 | 217: let var 'Tnew' := 0 187 | 218:} 188 | 220:if '$parThis_0004' { 189 | 221: set bit '$parOut_0000' 190 | 222:} 191 | 223:let bit '$parThis_0004' := '$rung_top' 192 | 224:# start series [ 193 | 225:# ELEM_CTC 194 | 226:if '$parThis_0004' { 195 | 227: clear bit '$parThis_0004' 196 | 228: if not '$oneShot_0001_CTC_Ccnt' { 197 | 229: set bit '$oneShot_0001_CTC_Ccnt' 198 | 230: increment 'Ccnt' 199 | 231: if 'Ccnt' < 51 { 200 | 232: } else { 201 | 233: let var 'Ccnt' := 0 202 | 234: set bit '$parThis_0004' 203 | 235: } 204 | 236: } 205 | 237:} else { 206 | 238: clear bit '$oneShot_0001_CTC_Ccnt' 207 | 239:} 208 | 241:# ELEM_OPEN 209 | 242:clear bit '$parThis_0004' 210 | 244:# ] finish series 211 | 245:if '$parThis_0004' { 212 | 246: set bit '$parOut_0000' 213 | 247:} 214 | 248:let bit '$rung_top' := '$parOut_0000' 215 | 249:# ] finish parallel 216 | 250:# start parallel [ 217 | 251:let bit '$parThis_0005' := '$rung_top' 218 | 252:# ELEM_COIL 219 | 253:if '$parThis_0005' { 220 | 254: clear bit 'Rclock' 221 | 255:} else { 222 | 256: set bit 'Rclock' 223 | 257:} 224 | 259:# ELEM_MUL 225 | 260:if '$rung_top' { 226 | 261: let var '$scratch2' := 5 227 | 262: let var 'Pblink' := 'Ccnt' * '$scratch2' 228 | 263:} 229 | 265:# ] finish parallel 230 | 266:# ] finish series 231 | -------------------------------------------------------------------------------- /ldmicro/LDuino-validv2.xint: -------------------------------------------------------------------------------- 1 | $$IO 23 35 2 | 0 Ccnt 0 0 0 00000 3 | 1 Padc 0 0 0 00000 4 | 2 Pblink 0 0 0 00000 5 | 3 Pduty 0 0 0 00000 6 | 4 Ccnt 0 0 0 00000 7 | 5 XA1 1 55 0 00000 8 | 6 YD1 2 3 0 00000 9 | 7 YD2 2 4 0 00000 10 | 8 YD3 2 5 0 00000 11 | 9 AA0 3 54 0 00000 12 | 10 P0 4 2 0 00000 13 | 11 P1 4 6 0 00000 14 | 12 P2 4 7 0 00000 15 | 13 Rblink 0 0 0 00000 16 | 14 Rclock 0 0 0 00000 17 | 15 Tdelay 0 0 0 00000 18 | 16 Tnew 0 0 0 00000 19 | 17 Ton 0 0 0 00000 20 | 18 Toff 0 0 0 00000 21 | 19 Imb0 5 0 1 00000 22 | 20 Mmb0 6 0 1 00000 23 | 21 Mmb1 6 0 1 00001 24 | 22 Hmb3 7 0 1 00000 25 | $$LDcode 320 26 | 01170318173305020218321804040332 27 | 0003191832190B340F320004060F0219 28 | A004040F000003081903181732050202 29 | 1832180404034B0001173217050C03E8 30 | 030A01173217020B090117321708041A 31 | 04000A01091A01173217050C01E8030B 32 | 0117320D02021732170B341132000406 33 | 110217A00404110000331B0404120F00 34 | 011B33170B34120F000406120117A004 35 | 04120000031C17030D1C031C1703141C 36 | 031C1703061C031C17321C0B321D0402 37 | 1CA002011DA002021D321C0206160117 38 | 3313020217031E1703071E031E170315 39 | 1E01173217050C02E8030C0117330E02 40 | 0217021F03201732200B341002000406 41 | 100220A00404100000322002011F0320 42 | 17322018022033211101210600340033 43 | 0002A006040000000120A00202210220 44 | 322002011F03171F032217322204020E 45 | A002010E321708041A05000902001AFF 46 | $$cycle 10000 us 47 | -------------------------------------------------------------------------------- /lduino_engine.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Frederic Rible 2 | // 3 | // This file is part of LDuino, an Arduino based PLC software compatible with LDmicro. 4 | // 5 | // LDuino is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // LDuino is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with LDmicro. If not, see . 17 | 18 | #include "Config.h" 19 | #include "lduino_engine.h" 20 | #include "sysinfo.h" 21 | 22 | #define INT_SET_BIT 1 23 | #define INT_CLEAR_BIT 2 24 | #define INT_COPY_BIT_TO_BIT 3 25 | #define INT_SET_VARIABLE_TO_LITERAL 4 26 | #define INT_SET_VARIABLE_TO_VARIABLE 5 27 | #define INT_INCREMENT_VARIABLE 6 28 | #define INT_SET_VARIABLE_ADD 7 29 | #define INT_SET_VARIABLE_SUBTRACT 8 30 | #define INT_SET_VARIABLE_MULTIPLY 9 31 | #define INT_SET_VARIABLE_DIVIDE 10 32 | #define INT_READ_ADC 11 33 | #define INT_SET_PWM 12 34 | 35 | #define INT_IF_BIT_SET 50 36 | #define INT_IF_BIT_CLEAR 51 37 | #define INT_IF_VARIABLE_LES_LITERAL 52 38 | #define INT_IF_VARIABLE_EQUALS_VARIABLE 53 39 | #define INT_IF_VARIABLE_GRT_VARIABLE 54 // obsolete 40 | 41 | #define INT_ELSE 60 + 100 42 | #define INT_END_IF 61 + 100 43 | 44 | #define INT_END_OF_PROGRAM 255 45 | 46 | LDuino_engine::LDuino_engine() 47 | { 48 | mb = NULL; 49 | Program = NULL; 50 | IO = NULL; 51 | Values = NULL; 52 | ClearProgram(); 53 | EEPROM_ProgramLen = 0; 54 | LoadConfig(); 55 | _status = "Booting"; 56 | } 57 | 58 | void LDuino_engine::SetModbus(Modbus * mb) 59 | { 60 | this->mb = mb; 61 | ConfigureModbus(); 62 | } 63 | 64 | void LDuino_engine::ClearProgram(void) 65 | { 66 | ProgramRunning = false; 67 | ProgramReady = false; 68 | LoaderState = st_init; 69 | pc = 0; 70 | line_length = 0; 71 | time = 0; 72 | nbProgram = 0; 73 | nbIO = 0; 74 | total_nbIO = 0; 75 | if (Program) delete[] Program; 76 | if (IO) delete[] IO; 77 | if (Values) delete[] Values; 78 | Program = NULL; 79 | IO = NULL; 80 | Values = NULL; 81 | } 82 | 83 | int LDuino_engine::HexDigit(int c) 84 | { 85 | if ((c >= '0') && (c <= '9')) { 86 | return c - '0'; 87 | } 88 | else if ((c >= 'a') && (c <= 'f')) { 89 | return 10 + (c - 'a'); 90 | } 91 | else if ((c >= 'A') && (c <= 'F')) { 92 | return 10 + (c - 'A'); 93 | } 94 | return -1; 95 | } 96 | 97 | LDuino_engine::state LDuino_engine::ChangeState(char * line) 98 | { 99 | if (LoaderState == st_error) return st_error; 100 | if (strstr(line, "$$LDcode")) LoaderState = st_LDcode; 101 | else if (strstr(line, "$$IO")) LoaderState = st_IO; 102 | else if (strstr(line, "$$cycle")) LoaderState = st_cycle; 103 | return LoaderState; 104 | } 105 | 106 | void LDuino_engine::LoadProgramLine(char *line) 107 | { 108 | line = strtok(line, "\r\n"); 109 | ChangeState(line); 110 | 111 | switch (LoaderState) { 112 | case st_LDcode: 113 | { 114 | if (line[0] == '$') { 115 | // $$LDcode program_size 116 | strtok(line, " "); 117 | char *p = strtok(NULL, " "); 118 | if (!p) goto err; 119 | nbProgram = atoi(p); 120 | Program = new BYTE[nbProgram](); 121 | Serial << F("nbProgram=") << nbProgram << "\n"; 122 | break; 123 | } 124 | 125 | for (char *t = line; t[0] >= 32 && t[1] >= 32; t += 2) { 126 | Program[pc++] = HexDigit(t[1]) | (HexDigit(t[0]) << 4); 127 | Serial << F("New Opcode[") << pc - 1 << "]=" << Program[pc - 1] << '\n'; 128 | } 129 | break; 130 | } 131 | case st_IO: 132 | { 133 | if (line[0] == '$') { 134 | // $$IO nb_named_IO total_nb_IO 135 | strtok(line, " "); 136 | 137 | char *p = strtok(NULL, " "); 138 | if (!p) goto err; 139 | nbIO = atoi(p); 140 | IO = new IO_t[nbIO](); 141 | 142 | p = strtok(NULL, " "); 143 | if (!p) goto err; 144 | total_nbIO = atoi(p); 145 | Values = new SWORD[total_nbIO](); 146 | 147 | Serial << F("nbIO=") << nbIO << F(" total_nbIO=") << total_nbIO << "\n"; 148 | break; 149 | } 150 | 151 | // 0 Xin 7 6 0 0 152 | Serial << line << '\n'; 153 | char *p; 154 | p = strtok(line, " "); // Addr 155 | if (!p) goto err; 156 | int addr = atoi(p); 157 | if (addr < 0 || addr >= nbIO) goto err; 158 | strtok(NULL, " "); // Skip name 159 | p = strtok(NULL, ""); 160 | if (!p) goto err; 161 | sscanf(p, "%d %d %d %d", 162 | &IO[addr].type, 163 | &IO[addr].pin, 164 | &IO[addr].ModbusSlave, 165 | &IO[addr].ModbusOffset); 166 | Serial << F("New IO[") << addr << F("]:") 167 | << F(" type=") << IO[addr].type 168 | << F(" pin=") << IO[addr].pin 169 | << F(" ModbusSlave=") << IO[addr].ModbusSlave 170 | << F(" ModbusOffset=") << IO[addr].ModbusOffset 171 | <<'\n'; 172 | break; 173 | } 174 | case st_cycle: 175 | { 176 | // $$cycle 10000 us 177 | cycle_ms = atoi(line + 7) / 1000; 178 | ConfigureModbus(); 179 | ProgramReady = true; 180 | ProgramRunning = true; 181 | IO_Polling = true; 182 | D(Serial.println("Program Ready")); 183 | D(Serial.print("cycle time (ms): ")); 184 | D(Serial.println(cycle_ms)); 185 | SaveConfig(); 186 | break; 187 | } 188 | } 189 | 190 | return; 191 | 192 | err: 193 | LoaderState = st_error; 194 | return; 195 | } 196 | 197 | void LDuino_engine::LoadProgramLine(char c) 198 | { 199 | if (line_length < sizeof(line)) line[line_length++] = c; 200 | if (c == '\n' && line_length > 0) { 201 | line[line_length] = 0; 202 | LoadProgramLine(line); 203 | line_length = 0; 204 | } 205 | } 206 | 207 | void LDuino_engine::ConfigureModbus(void) 208 | { 209 | if (!mb) return; 210 | 211 | mb->clearRegs(); 212 | 213 | for (int addr = 0; addr < nbIO; addr++) { 214 | switch (IO[addr].type) { 215 | case XIO_TYPE_MODBUS_COIL: 216 | Serial << F("Add Modbus Contact ") << IO[addr].ModbusOffset << '\n'; 217 | mb->addIsts(IO[addr].ModbusOffset); // We are slave, so coil on master side is contact on our side 218 | break; 219 | case XIO_TYPE_MODBUS_CONTACT: 220 | Serial << F("Add Modbus Coil ") << IO[addr].ModbusOffset << '\n'; 221 | mb->addCoil(IO[addr].ModbusOffset); // We are slave, so coil on master side is contact on our side 222 | break; 223 | case XIO_TYPE_MODBUS_HREG: 224 | Serial << F("Add Modbus Hreg ") << IO[addr].ModbusOffset << '\n'; 225 | mb->addHreg(IO[addr].ModbusOffset); 226 | break; 227 | } 228 | } 229 | } 230 | 231 | void LDuino_engine::InterpretOneCycle(void) 232 | { 233 | if (!ProgramReady || !ProgramRunning) return; 234 | 235 | for (int pc = 0;;) { 236 | D(Serial << "opcode[" << String(pc, HEX) << "]=" << String(Program[pc], HEX) << "\n"); 237 | switch (Program[pc]) { 238 | case INT_SET_BIT: 239 | WRITE_BIT(Program[pc+1], 1); 240 | pc += 2; 241 | break; 242 | 243 | case INT_CLEAR_BIT: 244 | WRITE_BIT(Program[pc + 1], 0); 245 | pc += 2; 246 | break; 247 | 248 | case INT_COPY_BIT_TO_BIT: 249 | WRITE_BIT(Program[pc+1], READ_BIT(Program[pc + 2])); 250 | pc += 3; 251 | break; 252 | 253 | case INT_SET_VARIABLE_TO_LITERAL: 254 | WRITE_INT(Program[pc+1], Program[pc + 2] + (Program[pc + 3] << 8)); 255 | pc += 4; 256 | break; 257 | 258 | case INT_SET_VARIABLE_TO_VARIABLE: 259 | WRITE_INT(Program[pc+1], READ_INT(Program[pc + 2])); 260 | pc += 3; 261 | break; 262 | 263 | case INT_INCREMENT_VARIABLE: 264 | WRITE_INT(Program[pc+1], READ_INT(Program[pc+1]) + 1); 265 | pc += 2; 266 | break; 267 | 268 | case INT_SET_VARIABLE_ADD: 269 | WRITE_INT(Program[pc+1], READ_INT(Program[pc + 2]) + READ_INT(Program[pc + 3])); 270 | pc += 4; 271 | break; 272 | 273 | case INT_SET_VARIABLE_SUBTRACT: 274 | WRITE_INT(Program[pc + 1], READ_INT(Program[pc + 2]) - READ_INT(Program[pc + 3])); 275 | pc += 4; 276 | break; 277 | 278 | case INT_SET_VARIABLE_MULTIPLY: 279 | WRITE_INT(Program[pc + 1], READ_INT(Program[pc + 2]) * READ_INT(Program[pc + 3])); 280 | pc += 4; 281 | break; 282 | 283 | case INT_SET_VARIABLE_DIVIDE: 284 | if (READ_INT(Program[pc + 3]) != 0) { 285 | WRITE_INT(Program[pc + 1], READ_INT(Program[pc + 2]) / READ_INT(Program[pc + 3])); 286 | } 287 | pc += 4; 288 | break; 289 | 290 | case INT_SET_PWM: 291 | WRITE_PWM(Program[pc + 4], Values[Program[pc + 1]]); // PWM frequency is ignored 292 | pc += 5; 293 | break; 294 | 295 | case INT_READ_ADC: 296 | READ_ADC(Program[pc + 1]); 297 | pc += 2; 298 | break; 299 | 300 | case INT_IF_BIT_SET: 301 | if (!READ_BIT(Program[pc+1])) pc += Program[pc + 2]; 302 | pc += 3; 303 | break; 304 | 305 | case INT_IF_BIT_CLEAR: 306 | if (READ_BIT(Program[pc + 1])) pc += Program[pc + 2]; 307 | pc += 3; 308 | break; 309 | 310 | case INT_IF_VARIABLE_LES_LITERAL: 311 | if (!(READ_INT(Program[pc + 1]) < (Program[pc + 2] + (Program[pc + 3] << 8)))) pc += Program[pc + 4]; 312 | pc += 5; 313 | break; 314 | 315 | case INT_IF_VARIABLE_EQUALS_VARIABLE: 316 | if (!(READ_INT(Program[pc + 1]) == READ_INT(Program[pc + 2]))) pc += Program[pc + 3]; 317 | pc += 4; 318 | break; 319 | 320 | case INT_IF_VARIABLE_GRT_VARIABLE: 321 | if (!(READ_INT(Program[pc + 1]) > READ_INT(Program[pc + 2]))) pc += Program[pc + 3]; 322 | pc += 4; 323 | break; 324 | 325 | case INT_ELSE: 326 | pc += Program[pc+1]; 327 | pc += 2; 328 | break; 329 | 330 | case INT_END_OF_PROGRAM: 331 | return; 332 | 333 | default: 334 | Serial.print("Unknown opcode: 0x"); 335 | Serial.print(Program[pc], HEX); 336 | Serial.println(""); 337 | Serial.print("PC: 0x"); 338 | Serial.print(pc, HEX); 339 | Serial.println(""); 340 | ProgramRunning = false; 341 | return; 342 | } 343 | } 344 | } 345 | 346 | void LDuino_engine::Engine(void) 347 | { 348 | int count = 10; 349 | 350 | if (time == 0) { 351 | time = millis(); 352 | return; 353 | } 354 | 355 | while (time + cycle_ms < millis() && count-- > 0) { 356 | unsigned long ts = micros(); 357 | InterpretOneCycle(); 358 | processing_time = micros() - ts; 359 | time = time + cycle_ms; 360 | #if DEBUG 361 | while (!Serial.available()); 362 | Serial.read(); 363 | #endif 364 | } 365 | } 366 | 367 | void LDuino_engine::PrintStats(Print & stream) 368 | { 369 | stream << F("Program running: ") << (ProgramRunning ? F("yes") : F("no")) << '\n'; 370 | stream << F("EEPROM: ") << EEPROM_ProgramLen << '/' << EEPROM.length() << F(" bytes used\n"); 371 | stream << F("Opcodes: ") << nbProgram << '\n'; 372 | stream << F("IO vars: ") << nbIO << '\n'; 373 | stream << F("Internal vars: ") << total_nbIO - nbIO << '\n'; 374 | stream << F("Cycle: ") << cycle_ms << F(" ms\n"); 375 | stream << F("Processing time: ") << processing_time << F(" us\n"); 376 | 377 | stream << F("\nVariables dump\n"); 378 | 379 | for (int i = 0; i < total_nbIO; i++) { 380 | char buf[20]; 381 | sprintf(buf, "%3d: %6d", i, Values[i]); 382 | stream << buf << "\n"; 383 | } 384 | } 385 | 386 | int LDuino_engine::GetType(int pin, signed short *value) 387 | { 388 | for (int addr = 0; addr < nbIO; addr++) { 389 | if (IO[addr].pin == pin) { 390 | if (!ProgramRunning && IO_Polling) { 391 | switch (IO[addr].type) { 392 | case XIO_TYPE_READ_ADC: 393 | READ_ADC(addr); 394 | break; 395 | default: 396 | READ_BIT(addr); 397 | break; 398 | } 399 | } 400 | *value = Values[addr]; 401 | return IO[addr].type; 402 | } 403 | } 404 | 405 | *value = digitalRead(pin); 406 | return XIO_TYPE_PENDING; 407 | } 408 | 409 | void LDuino_engine::setPWM(int pin, signed short value) 410 | { 411 | for (int addr = 0; addr < nbIO; addr++) { 412 | if (IO[addr].pin == pin) { 413 | Values[addr] = value; 414 | if (IO_Polling) analogWrite(pin, value); 415 | break; 416 | } 417 | } 418 | } 419 | 420 | void LDuino_engine::setAnalogInput(int pin, signed short value) 421 | { 422 | for (int addr = 0; addr < nbIO; addr++) { 423 | if (IO[addr].pin == pin) { 424 | Values[addr] = value; 425 | break; 426 | } 427 | } 428 | } 429 | 430 | void LDuino_engine::toggleDigitalOutput(int pin) 431 | { 432 | for (int addr = 0; addr < nbIO; addr++) { 433 | if (IO[addr].pin == pin) { 434 | Values[addr] = !Values[addr]; 435 | if (IO_Polling) digitalWrite(pin, Values[addr]); 436 | return; 437 | } 438 | } 439 | if (IO_Polling) digitalWrite(pin, !digitalRead(pin)); 440 | } 441 | 442 | void LDuino_engine::toggleDigitalInput(int pin) 443 | { 444 | for (int addr = 0; addr < nbIO; addr++) { 445 | if (IO[addr].pin == pin) { 446 | Values[addr] = !Values[addr]; 447 | return; 448 | } 449 | } 450 | } 451 | 452 | // Read digital pins, avoid pins that are configure for PWM or analog input 453 | 454 | void LDuino_engine::XML_DumpDigitalPins(xmlstring &str, int first, int last, int offset) 455 | { 456 | bool comma = false; 457 | signed short value; 458 | 459 | for (short r = first; r <= last; r++) { 460 | short pin = r + offset; 461 | short rc = GetType(pin, &value); 462 | switch (rc) { 463 | case XIO_TYPE_DIG_INPUT: 464 | case XIO_TYPE_DIG_OUTPUT: 465 | case XIO_TYPE_PENDING: 466 | if (comma) str += ','; 467 | str += String(r) + ':' + String(value) + ':' + String(rc != XIO_TYPE_PENDING); 468 | comma = true; 469 | break; 470 | } 471 | } 472 | } 473 | 474 | void LDuino_engine::XML_DumpAnalogPins(xmlstring &str, int first, int last, int offset) 475 | { 476 | bool comma = false; 477 | signed short value; 478 | 479 | for (short r = first; r <= last; r++) { 480 | short pin = r + offset; 481 | switch (GetType(pin, &value)) { 482 | case XIO_TYPE_READ_ADC: 483 | case XIO_TYPE_PWM_OUTPUT: 484 | if (comma) str += ','; 485 | str += String(r) + ':' + String(value); 486 | comma = true; 487 | break; 488 | } 489 | } 490 | } 491 | 492 | void LDuino_engine::XML_State(Print & stream) 493 | { 494 | xmlstring str; 495 | 496 | str += F("\n"); 497 | str += F("\n"); 498 | str.catTag(F("running"), ProgramRunning); 499 | str.catTag(F("io_polling"), IO_Polling); 500 | str.catTag(F("program_len"), EEPROM_ProgramLen); 501 | str.catTag(F("eeprom_len"), EEPROM.length()); 502 | str.catTag(F("opcodes"), nbProgram); 503 | str.catTag(F("io_nb"), nbIO); 504 | str.catTag(F("internal_vars"), total_nbIO - nbIO); 505 | str.catTag(F("cycle"), cycle_ms); 506 | str.catTag(F("processing"), processing_time); 507 | str.catTag(F("unusedRam"), sysinfo::unusedRam()); 508 | str.catTag(F("_status"), _status); 509 | 510 | #if 0 511 | buf += F("\n"); 512 | 513 | for (int i = 0; i < total_nbIO; i++) { 514 | buf += F("' + Values[i] + F("\n"); 515 | } 516 | 517 | buf += F("\n"); 518 | #endif 519 | 520 | str += F("\n"); 521 | XML_DumpDigitalPins(str, 0, 11, 2); 522 | str += F("\n"); 523 | 524 | str += F(""); 525 | XML_DumpDigitalPins(str, 0, 9, 22); 526 | str += F("\n"); 527 | 528 | str += F(""); 529 | XML_DumpDigitalPins(str, 0, 9, 54); 530 | str += F("\n"); 531 | 532 | str += F(""); 533 | XML_DumpAnalogPins(str, 0, 9, 54); 534 | str += F("\n"); 535 | 536 | str += F(""); 537 | XML_DumpAnalogPins(str, 0, 9, 2); 538 | str += F("\n"); 539 | 540 | str += F("\n"); 541 | stream << str; 542 | } 543 | 544 | void LDuino_engine::SaveConfig() 545 | { 546 | int p = PROGRAM_OFFSET; 547 | 548 | version = PLC_VERSION; 549 | EEWRITE(version); 550 | EEWRITE(cycle_ms); 551 | EEWRITE(nbProgram); 552 | EEWRITE(nbIO); 553 | EEWRITE(total_nbIO); 554 | 555 | for (int i = 0; i < nbIO; i++) { 556 | EEWRITE(IO[i]); 557 | } 558 | 559 | for (int i = 0; i < nbProgram; i++) { 560 | EEWRITE(Program[i]); 561 | } 562 | 563 | EEPROM_ProgramLen = p; 564 | UpdateCRC(); 565 | } 566 | 567 | void LDuino_engine::LoadConfig() 568 | { 569 | int p = PROGRAM_OFFSET; 570 | 571 | if (!CheckCRC()) return; 572 | 573 | EEREAD(version); 574 | if (version != PLC_VERSION) return; 575 | 576 | EEREAD(cycle_ms); 577 | EEREAD(nbProgram); 578 | EEREAD(nbIO); 579 | EEREAD(total_nbIO); 580 | 581 | if (nbProgram > 2048) return; 582 | 583 | Program = new BYTE[nbProgram](); 584 | IO = new IO_t[nbIO](); 585 | Values = new SWORD[total_nbIO](); 586 | 587 | for (int i = 0; i < nbIO; i++) { 588 | EEREAD(IO[i]); 589 | } 590 | 591 | for (int i = 0; i < nbProgram; i++) { 592 | EEREAD(Program[i]); 593 | } 594 | 595 | EEPROM_ProgramLen = p; 596 | ProgramRunning = true; 597 | ProgramReady = true; 598 | IO_Polling = true; 599 | } 600 | 601 | void LDuino_engine::WRITE_BIT(BYTE addr, boolean value) 602 | { 603 | if (Values[addr] == value) return; 604 | if (addr < nbIO) { 605 | switch (IO[addr].type) { 606 | case XIO_TYPE_DIG_OUTPUT: 607 | if (IO_Polling) digitalWrite(IO[addr].pin, value); 608 | break; 609 | case XIO_TYPE_MODBUS_COIL: 610 | if (mb) mb->Ists(IO[addr].ModbusOffset, value); 611 | break; 612 | } 613 | } 614 | 615 | Values[addr] = value; 616 | D(Serial << "write bit[" << addr << "] " << value << "\n"); 617 | } 618 | 619 | boolean LDuino_engine::READ_BIT(BYTE addr) 620 | { 621 | if (addr < nbIO) { 622 | switch (IO[addr].type) { 623 | case XIO_TYPE_DIG_INPUT: 624 | if (IO_Polling) Values[addr] = digitalRead(IO[addr].pin); 625 | break; 626 | case XIO_TYPE_MODBUS_COIL: 627 | if (mb) Values[addr] = mb->Ists(IO[addr].ModbusOffset); 628 | break; 629 | case XIO_TYPE_MODBUS_CONTACT: 630 | if (mb) Values[addr] = mb->Coil(IO[addr].ModbusOffset); 631 | break; 632 | } 633 | } 634 | 635 | D(Serial << "read bit[" << addr << "] " << Values[addr] << '\n'); 636 | return Values[addr]; 637 | } 638 | 639 | void LDuino_engine::WRITE_INT(BYTE addr, SWORD value) 640 | { 641 | if (Values[addr] == value) return; 642 | 643 | if (addr < nbIO) { 644 | switch (IO[addr].type) { 645 | case XIO_TYPE_MODBUS_HREG: 646 | if (mb) mb->Hreg(IO[addr].ModbusOffset, value); 647 | break; 648 | } 649 | } 650 | 651 | Values[addr] = value; 652 | D(Serial << "write int[" << addr << "] " << value << '\n'); 653 | } 654 | 655 | LDuino_engine::SWORD LDuino_engine::READ_INT(BYTE addr) 656 | { 657 | if (addr < nbIO) { 658 | switch (IO[addr].type) { 659 | case XIO_TYPE_MODBUS_HREG: 660 | if (mb) Values[addr] = mb->Hreg(IO[addr].ModbusOffset); 661 | break; 662 | } 663 | } 664 | 665 | D(Serial << "read int[" << addr << "] " << Values[addr] << '\n'); 666 | return Values[addr]; 667 | } 668 | 669 | void LDuino_engine::WRITE_PWM(BYTE addr, SWORD value) 670 | { 671 | if (Values[addr] == value) return; 672 | if (IO_Polling) analogWrite(IO[addr].pin, value); 673 | Values[addr] = value; 674 | } 675 | 676 | void LDuino_engine::READ_ADC(BYTE addr) 677 | { 678 | if (IO_Polling) Values[addr] = analogRead(IO[addr].pin); 679 | } 680 | -------------------------------------------------------------------------------- /lduino_engine.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Frederic Rible 2 | // 3 | // This file is part of LDuino, an Arduino based PLC software compatible with LDmicro. 4 | // 5 | // LDuino is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // LDuino is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with LDmicro. If not, see . 17 | 18 | #ifndef _LDUINO_h 19 | #define _LDUINO_h 20 | 21 | #include "Modbus.h" 22 | #include "Config.h" 23 | #include "xmlstring.h" 24 | 25 | #if defined(ARDUINO) && ARDUINO >= 100 26 | #include "arduino.h" 27 | #else 28 | #include "WProgram.h" 29 | #endif 30 | 31 | #define MAX_PHYS_PINS 128 32 | #define MAX_INT_RELAYS 64 33 | #define MAX_MODBUS_COILS 64 34 | 35 | #define PLC_VERSION 2 36 | 37 | #include "Streaming.h" 38 | 39 | #if DEBUG 40 | #define D(x) (x) 41 | #else 42 | #define D(x) 43 | #endif 44 | 45 | class LDuino_engine : public Config { 46 | public: 47 | LDuino_engine(); 48 | void SetModbus(Modbus *mb); 49 | void ClearProgram(void); 50 | void LoadProgramLine(char c); 51 | void Engine(void); 52 | unsigned long GetTime() { return time; }; 53 | void PrintStats(Print & stream); 54 | int GetType(int pin, signed short * value); 55 | 56 | void setPWM(int pin, signed short value); 57 | void setAnalogInput(int pin, signed short value); 58 | void toggleDigitalOutput(int pin); 59 | void toggleDigitalInput(int pin); 60 | 61 | void XML_DumpDigitalPins(xmlstring & str, int first, int last, int offset); 62 | void XML_DumpAnalogPins(xmlstring & str, int first, int last, int offset); 63 | void XML_State(Print & stream); 64 | void SaveConfig(); 65 | bool getProgramReady(void) { return ProgramReady; }; 66 | void ToggleProgramRunning(void) { if (ProgramReady) ProgramRunning = !ProgramRunning; } 67 | void ToggleIO_Polling(void) { IO_Polling = !IO_Polling; } 68 | 69 | short getCycleMS() { return cycle_ms; } 70 | void setStatus(String status) { _status = status; } 71 | String getStatus(void) { return _status; } 72 | private: 73 | void LoadProgramLine(char *line); 74 | void ConfigureModbus(void); 75 | void InterpretOneCycle(void); 76 | int HexDigit(int c); 77 | void LoadConfig(); 78 | void PrintPin(Print & stream, char prefix, int index, int value); 79 | 80 | typedef unsigned char BYTE; // 8-bit unsigned 81 | typedef unsigned short WORD; // 16-bit unsigned 82 | typedef signed short SWORD; // 16-bit signed 83 | 84 | 85 | #define XIO_TYPE_PENDING 0 86 | #define XIO_TYPE_DIG_INPUT 1 87 | #define XIO_TYPE_DIG_OUTPUT 2 88 | #define XIO_TYPE_READ_ADC 3 89 | #define XIO_TYPE_PWM_OUTPUT 4 90 | #define XIO_TYPE_MODBUS_CONTACT 5 91 | #define XIO_TYPE_MODBUS_COIL 6 92 | #define XIO_TYPE_MODBUS_HREG 7 93 | 94 | 95 | typedef struct PlcProgramSingleIoTag { 96 | BYTE type; 97 | BYTE pin; 98 | BYTE ModbusSlave; 99 | WORD ModbusOffset; 100 | } IO_t; 101 | 102 | void WRITE_BIT(BYTE addr, boolean value); 103 | boolean READ_BIT(BYTE addr); 104 | void WRITE_INT(BYTE addr, SWORD value); 105 | SWORD READ_INT(BYTE addr); 106 | void WRITE_PWM(BYTE addr, SWORD value); 107 | void READ_ADC(BYTE addr); 108 | 109 | Modbus *mb; 110 | 111 | BYTE version; 112 | SWORD cycle_ms; 113 | SWORD nbProgram; 114 | BYTE nbIO; 115 | BYTE total_nbIO; 116 | 117 | BYTE *Program; 118 | IO_t *IO; 119 | SWORD *Values; 120 | 121 | SWORD EEPROM_ProgramLen; 122 | 123 | boolean ProgramRunning; 124 | boolean ProgramReady; 125 | boolean IO_Polling; // true if physical I/O are polled, false for simulation mode 126 | 127 | /* 128 | $$IO 129 | $$LDcode 130 | $$cycle 10000 us 131 | */ 132 | 133 | enum state {st_init, st_LDcode, st_IO, st_cycle, st_error} LoaderState; 134 | state ChangeState(char *line); 135 | 136 | int pc; 137 | unsigned long time; 138 | unsigned long processing_time; 139 | 140 | char line[80]; 141 | char line_length; 142 | 143 | String _status; 144 | }; 145 | 146 | 147 | #endif 148 | 149 | -------------------------------------------------------------------------------- /malloc.c: -------------------------------------------------------------------------------- 1 | #if defined(__AVR__) 2 | 3 | /* Indicate that we are ISR safe. */ 4 | #define __ISR_SAFE_MALLOC__ 1 5 | 6 | 7 | /* Copyright (c) 2002, 2004, 2010 Joerg Wunsch 8 | Copyright (c) 2010 Gerben van den Broeke 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions are met: 13 | 14 | * Redistributions of source code must retain the above copyright 15 | notice, this list of conditions and the following disclaimer. 16 | 17 | * Redistributions in binary form must reproduce the above copyright 18 | notice, this list of conditions and the following disclaimer in 19 | the documentation and/or other materials provided with the 20 | distribution. 21 | 22 | * Neither the name of the copyright holders nor the names of 23 | contributors may be used to endorse or promote products derived 24 | from this software without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 30 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 | POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | 40 | /* $Id: malloc.c 2149 2010-06-09 20:45:37Z joerg_wunsch $ */ 41 | 42 | #include 43 | #include 44 | 45 | #include "sectionname.h" 46 | #include "stdlib_private.h" 47 | 48 | #include 49 | #include 50 | #define XATOMIC_BLOCK(type) type, __ToDo; for (__ToDo = __iCliRetVal(); \ 51 | __ToDo ; __ToDo = 0 ) 52 | 53 | void (*_malloc_exception)(size_t) = NULL; 54 | 55 | /* 56 | * Exported interface: 57 | * 58 | * When extending the data segment, the allocator will not try to go 59 | * beyond the current stack limit, decreased by __malloc_margin bytes. 60 | * Thus, all possible stack frames of interrupt routines that could 61 | * interrupt the current function, plus all further nested function 62 | * calls must not require more stack space, or they'll risk to collide 63 | * with the data segment. 64 | */ 65 | 66 | /* May be changed by the user only before the first malloc() call. */ 67 | 68 | size_t __malloc_margin = 128; 69 | char *__malloc_heap_start = &__heap_start; 70 | char *__malloc_heap_end = &__heap_end; 71 | 72 | char *__brkval; 73 | struct __freelist *__flp; 74 | 75 | static 76 | void * 77 | _malloc(size_t len) { 78 | struct __freelist *fp1, *fp2, *sfp1, *sfp2; 79 | char *cp; 80 | size_t s, avail; 81 | 82 | 83 | /* 84 | * Our minimum chunk size is the size of a pointer (plus the 85 | * size of the "sz" field, but we don't need to account for 86 | * this), otherwise we could not possibly fit a freelist entry 87 | * into the chunk later. 88 | */ 89 | if(len < sizeof(struct __freelist) - sizeof(size_t)) 90 | len = sizeof(struct __freelist) - sizeof(size_t); 91 | 92 | /* 93 | * First, walk the free list and try finding a chunk that 94 | * would match exactly. If we found one, we are done. While 95 | * walking, note down the smallest chunk we found that would 96 | * still fit the request -- we need it for step 2. 97 | * 98 | */ 99 | for(s = 0, fp1 = __flp, fp2 = 0; 100 | fp1; 101 | fp2 = fp1, fp1 = fp1->nx) { 102 | if(fp1->sz < len) 103 | continue; 104 | if(fp1->sz == len) { 105 | /* 106 | * Found it. Disconnect the chunk from the 107 | * freelist, and return it. 108 | */ 109 | if(fp2) 110 | fp2->nx = fp1->nx; 111 | else 112 | __flp = fp1->nx; 113 | return &(fp1->nx); 114 | } else { 115 | if(s == 0 || fp1->sz < s) { 116 | /* this is the smallest chunk found so far */ 117 | s = fp1->sz; 118 | sfp1 = fp1; 119 | sfp2 = fp2; 120 | } 121 | } 122 | } 123 | /* 124 | * Step 2: If we found a chunk on the freelist that would fit 125 | * (but was too large), look it up again and use it, since it 126 | * is our closest match now. Since the freelist entry needs 127 | * to be split into two entries then, watch out that the 128 | * difference between the requested size and the size of the 129 | * chunk found is large enough for another freelist entry; if 130 | * not, just enlarge the request size to what we have found, 131 | * and use the entire chunk. 132 | */ 133 | if(s) { 134 | if(s - len < sizeof(struct __freelist)) { 135 | /* Disconnect it from freelist and return it. */ 136 | if(sfp2) 137 | sfp2->nx = sfp1->nx; 138 | else 139 | __flp = sfp1->nx; 140 | return &(sfp1->nx); 141 | } 142 | /* 143 | * Split them up. Note that we leave the first part 144 | * as the new (smaller) freelist entry, and return the 145 | * upper portion to the caller. This saves us the 146 | * work to fix up the freelist chain; we just need to 147 | * fixup the size of the current entry, and note down 148 | * the size of the new chunk before returning it to 149 | * the caller. 150 | */ 151 | cp = (char *) sfp1; 152 | s -= len; 153 | cp += s; 154 | sfp2 = (struct __freelist *) cp; 155 | sfp2->sz = len; 156 | sfp1->sz = s - sizeof(size_t); 157 | return &(sfp2->nx); 158 | } 159 | /* 160 | * Step 3: If the request could not be satisfied from a 161 | * freelist entry, just prepare a new chunk. This means we 162 | * need to obtain more memory first. The largest address just 163 | * not allocated so far is remembered in the brkval variable. 164 | * Under Unix, the "break value" was the end of the data 165 | * segment as dynamically requested from the operating system. 166 | * Since we don't have an operating system, just make sure 167 | * that we don't collide with the stack. 168 | */ 169 | if(__brkval == NULL) 170 | __brkval = __malloc_heap_start; 171 | cp = __malloc_heap_end; 172 | if(cp == 0) 173 | cp = STACK_POINTER() - __malloc_margin; 174 | if(cp <= __brkval) 175 | /* 176 | * Memory exhausted. 177 | */ 178 | return NULL; 179 | avail = cp - __brkval; 180 | /* 181 | * Both tests below are needed to catch the case len >= 0xfffe. 182 | */ 183 | if(avail >= len && avail >= len + sizeof(size_t)) { 184 | fp1 = (struct __freelist *) __brkval; 185 | __brkval += len + sizeof(size_t); 186 | fp1->sz = len; 187 | return &(fp1->nx); 188 | } 189 | /* 190 | * Step 4: There's no help, just fail. :-/ 191 | */ 192 | if (_malloc_exception) _malloc_exception(len); 193 | return NULL; 194 | } 195 | 196 | static 197 | void 198 | _free(void *p) { 199 | struct __freelist *fp1, *fp2, *fpnew; 200 | char *cp1, *cp2, *cpnew; 201 | 202 | /* ISO C says free(NULL) must be a no-op */ 203 | if(p == NULL) 204 | return; 205 | 206 | if((char *)p < __malloc_heap_start) return; // Don't free, out of range. 207 | cpnew = p; 208 | cpnew -= sizeof(size_t); 209 | fpnew = (struct __freelist *) cpnew; 210 | fpnew->nx = NULL; 211 | 212 | /* 213 | * Trivial case first: if there's no freelist yet, our entry 214 | * will be the only one on it. If this is the last entry, we 215 | * can reduce __brkval instead. 216 | */ 217 | if(__flp == NULL) { 218 | if((char *) p + fpnew->sz == __brkval) 219 | __brkval = cpnew; 220 | else 221 | __flp = fpnew; 222 | return; 223 | } 224 | 225 | /* 226 | * Now, find the position where our new entry belongs onto the 227 | * freelist. Try to aggregate the chunk with adjacent chunks 228 | * if possible. 229 | */ 230 | for(fp1 = __flp, fp2 = NULL; 231 | fp1; 232 | fp2 = fp1, fp1 = fp1->nx) { 233 | if(fp1 < fpnew) 234 | continue; 235 | cp1 = (char *) fp1; 236 | fpnew->nx = fp1; 237 | if((char *) &(fpnew->nx) + fpnew->sz == cp1) { 238 | /* upper chunk adjacent, assimilate it */ 239 | fpnew->sz += fp1->sz + sizeof(size_t); 240 | fpnew->nx = fp1->nx; 241 | } 242 | if(fp2 == NULL) { 243 | /* new head of freelist */ 244 | __flp = fpnew; 245 | return; 246 | } 247 | break; 248 | } 249 | /* 250 | * Note that we get here either if we hit the "break" above, 251 | * or if we fell off the end of the loop. The latter means 252 | * we've got a new topmost chunk. Either way, try aggregating 253 | * with the lower chunk if possible. 254 | */ 255 | fp2->nx = fpnew; 256 | cp2 = (char *) &(fp2->nx); 257 | if(cp2 + fp2->sz == cpnew) { 258 | /* lower junk adjacent, merge */ 259 | fp2->sz += fpnew->sz + sizeof(size_t); 260 | fp2->nx = fpnew->nx; 261 | } 262 | /* 263 | * If there's a new topmost chunk, lower __brkval instead. 264 | */ 265 | for(fp1 = __flp, fp2 = NULL; 266 | fp1->nx != 0; 267 | fp2 = fp1, fp1 = fp1->nx) 268 | /* advance to entry just before end of list */; 269 | cp2 = (char *) &(fp1->nx); 270 | if(cp2 + fp1->sz == __brkval) { 271 | if(fp2 == NULL) 272 | /* Freelist is empty now. */ 273 | __flp = NULL; 274 | else 275 | fp2->nx = NULL; 276 | __brkval = cp2 - sizeof(size_t); 277 | } 278 | } 279 | 280 | static 281 | void * 282 | _realloc(void *ptr, size_t len) { 283 | struct __freelist *fp1, *fp2, *fp3, *ofp3; 284 | char *cp, *cp1; 285 | void *memp; 286 | size_t s, incr; 287 | 288 | /* Trivial case, required by C standard. */ 289 | if(ptr == NULL) 290 | return _malloc(len); 291 | 292 | if((char *)ptr < __malloc_heap_start) goto move_it; // Don't extend, and won't free. 293 | cp1 = (char *) ptr; 294 | cp1 -= sizeof(size_t); 295 | fp1 = (struct __freelist *) cp1; 296 | 297 | cp = (char *) ptr + len; /* new next pointer */ 298 | if (cp < cp1) { 299 | /* Pointer wrapped across top of RAM, fail. */ 300 | if (_malloc_exception) _malloc_exception(len); 301 | return NULL; 302 | } 303 | 304 | /* 305 | * See whether we are growing or shrinking. When shrinking, 306 | * we split off a chunk for the released portion, and call 307 | * free() on it. Therefore, we can only shrink if the new 308 | * size is at least sizeof(struct __freelist) smaller than the 309 | * previous size. 310 | */ 311 | if(len <= fp1->sz) { 312 | /* The first test catches a possible unsigned int 313 | * rollover condition. */ 314 | if(fp1->sz <= sizeof(struct __freelist) || 315 | len > fp1->sz - sizeof(struct __freelist)) 316 | return ptr; 317 | fp2 = (struct __freelist *) cp; 318 | fp2->sz = fp1->sz - len - sizeof(size_t); 319 | fp1->sz = len; 320 | _free(&(fp2->nx)); 321 | return ptr; 322 | } 323 | 324 | /* 325 | * If we get here, we are growing. First, see whether there 326 | * is space in the free list on top of our current chunk. 327 | */ 328 | incr = len - fp1->sz; 329 | cp = (char *) ptr + fp1->sz; 330 | fp2 = (struct __freelist *) cp; 331 | for(s = 0, ofp3 = NULL, fp3 = __flp; 332 | fp3; 333 | ofp3 = fp3, fp3 = fp3->nx) { 334 | if(fp3 == fp2 && fp3->sz + sizeof(size_t) >= incr) { 335 | /* found something that fits */ 336 | if(fp3->sz + sizeof(size_t) - incr > sizeof(struct __freelist)) { 337 | /* split off a new freelist entry */ 338 | cp = (char *) ptr + len; 339 | fp2 = (struct __freelist *) cp; 340 | fp2->nx = fp3->nx; 341 | fp2->sz = fp3->sz - incr; 342 | fp1->sz = len; 343 | } else { 344 | /* it just fits, so use it entirely */ 345 | fp1->sz += fp3->sz + sizeof(size_t); 346 | fp2 = fp3->nx; 347 | } 348 | if(ofp3) 349 | ofp3->nx = fp2; 350 | else 351 | __flp = fp2; 352 | return ptr; 353 | } 354 | /* 355 | * Find the largest chunk on the freelist while 356 | * walking it. 357 | */ 358 | if(fp3->sz > s) 359 | s = fp3->sz; 360 | } 361 | /* 362 | * If we are the topmost chunk in memory, and there was no 363 | * large enough chunk on the freelist that could be re-used 364 | * (by a call to malloc() below), quickly extend the 365 | * allocation area if possible, without need to copy the old 366 | * data. 367 | */ 368 | if(__brkval == (char *) ptr + fp1->sz && len > s) { 369 | cp1 = __malloc_heap_end; 370 | cp = (char *) ptr + len; 371 | if(cp1 == 0) 372 | cp1 = STACK_POINTER() - __malloc_margin; 373 | if(cp < cp1) { 374 | __brkval = cp; 375 | fp1->sz = len; 376 | return ptr; 377 | } 378 | /* If that failed, we are out of luck. */ 379 | if (_malloc_exception) _malloc_exception(len); 380 | return NULL; 381 | } 382 | 383 | /* 384 | * Call malloc() for a new chunk, then copy over the data, and 385 | * release the old region. 386 | */ 387 | move_it: 388 | if((memp = _malloc(len)) == NULL) 389 | return NULL; 390 | memcpy(memp, ptr, fp1->sz); 391 | _free(ptr); 392 | return memp; 393 | } 394 | 395 | /* thread/irq/task safe wrappers */ 396 | 397 | ATTRIBUTE_CLIB_SECTION 398 | void * 399 | malloc(size_t len) { 400 | register void *p; 401 | 402 | XATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 403 | p = _malloc(len); 404 | } 405 | return p; 406 | } 407 | 408 | ATTRIBUTE_CLIB_SECTION 409 | void 410 | free(void *p) { 411 | 412 | XATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 413 | _free(p); 414 | } 415 | } 416 | 417 | ATTRIBUTE_CLIB_SECTION 418 | void * 419 | realloc(void *ptr, size_t len) { 420 | register void *p; 421 | 422 | XATOMIC_BLOCK(ATOMIC_RESTORESTATE) { 423 | p = _realloc(ptr, len); 424 | } 425 | return p; 426 | } 427 | #endif 428 | -------------------------------------------------------------------------------- /mkfs.bat: -------------------------------------------------------------------------------- 1 | python3 tools/makefsdata.py 2 | pause 3 | -------------------------------------------------------------------------------- /plcweb.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Frederic Rible 2 | // 3 | // This file is part of LDuino, an Arduino based PLC software compatible with LDmicro. 4 | // 5 | // LDuino is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // LDuino is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with LDmicro. If not, see . 17 | 18 | #include "Arduino.h" 19 | 20 | #ifdef CONTROLLINO_MAXI 21 | #include 22 | #else 23 | #include 24 | #endif 25 | 26 | #include 27 | 28 | #include "TinyWebServer.h" 29 | #include "plcweb.h" 30 | #include "CircularBuffer.h" 31 | #include "lduino_engine.h" 32 | #include "Config.h" 33 | 34 | #include "httpd-fsdata.h" 35 | 36 | #if 0 37 | #include "utility/w5100.h" 38 | void W5100_reset(void) 39 | { 40 | Serial << "W5100 reset\n"; 41 | 42 | SPI.beginTransaction(SPI_ETHERNET_SETTINGS); 43 | W5100Class::writeMR(1 << 7); 44 | W5100Class::writeTMSR(0x55); 45 | W5100Class::writeRMSR(0x55); 46 | SPI.endTransaction(); 47 | 48 | Serial << "Transaction done\n"; 49 | 50 | uint8_t resetState; 51 | SPI.beginTransaction(SPI_ETHERNET_SETTINGS); 52 | do { 53 | resetState = W5100Class::readMR(); 54 | Serial << resetState << '\n'; 55 | delay(100); 56 | } while ((resetState & (1 << 7)) != 0); 57 | SPI.endTransaction(); 58 | 59 | delay(100); 60 | } 61 | #endif 62 | 63 | 64 | extern LDuino_engine lduino; 65 | extern IP_Config_t IP_Config; 66 | extern bool doReset; 67 | 68 | static boolean file_handler(TinyWebServer& web_server); 69 | static void manage_toggle(const char * path); 70 | static boolean getstate_handler(TinyWebServer& web_server); 71 | static boolean setconfig_handler(TinyWebServer& web_server); 72 | static boolean getconfig_handler(TinyWebServer& web_server); 73 | static boolean reboot_handler(TinyWebServer& web_server); 74 | 75 | static TinyWebServer::PathHandler handlers[] = { 76 | { "/upload", TinyWebServer::POST, &TinyWebPutHandler::put_handler }, 77 | { "/setconfig", TinyWebServer::POST, &setconfig_handler }, 78 | { "/getconfig.xml", TinyWebServer::GET, &getconfig_handler }, 79 | { "/reboot", TinyWebServer::GET, &reboot_handler }, 80 | { "/getstate.xml" "*", TinyWebServer::GET, &getstate_handler }, 81 | { "/" "*", TinyWebServer::GET, &file_handler }, 82 | { NULL }, 83 | }; 84 | 85 | static const char* headers[] = { 86 | "Content-Length", 87 | "Content-Type", 88 | "If-Modified-Since", 89 | NULL 90 | }; 91 | 92 | static TinyWebServer web = TinyWebServer(handlers, headers); 93 | 94 | #if 0 95 | static void main_page(Client& client, String status = "") 96 | { 97 | //index_html.print(client); 98 | String str((__FlashStringHelper *)index_html_flash); 99 | str.replace(F("%status"), status.length() > 0 ? status + '\n' : ""); 100 | str.replace(F("%useDHCP"), IP_Config.useDHCP ? "checked": ""); 101 | str.replace(F("%mac"), MAC2Ascii(IP_Config.mac_address)); 102 | str.replace(F("%ip"), IP2Ascii(IP_Config.local_ip)); 103 | str.replace(F("%subnet"), IP2Ascii(IP_Config.subnet)); 104 | str.replace(F("%dns"), IP2Ascii(IP_Config.dns_server)); 105 | str.replace(F("%gateway"), IP2Ascii(IP_Config.gateway)); 106 | str.replace(F("%modbus_baudrate"), String(IP_Config.modbus_baudrate)); 107 | client << str; 108 | lduino.PrintStats(client); 109 | client << F(""); 110 | } 111 | #endif 112 | 113 | static bool find_file(const char *path, const struct httpd_fsdata_segment **segment, int *len, bool *use_gzip) 114 | { 115 | const struct httpd_fsdata_file *p = httpd_fsroot; 116 | while (p) { 117 | char *n = (char*)pgm_read_word(&(p->name)); 118 | if (strcmp_P(path, n) == 0) { 119 | *segment = (const struct httpd_fsdata_segment *)pgm_read_word(&(p->first)); 120 | *len = pgm_read_word(&(p->len)); 121 | *use_gzip = pgm_read_byte(&(p->gzip)); 122 | return true; 123 | } 124 | p = (const struct httpd_fsdata_file *)pgm_read_word(&(p->next)); 125 | } 126 | return false; 127 | } 128 | 129 | static boolean file_handler(TinyWebServer& web_server) 130 | { 131 | const char *path = web_server.get_path(); 132 | Serial << "GET " << path << '\n'; 133 | 134 | if (strcmp(path, "/") == 0) path = "/index.html"; 135 | 136 | const struct httpd_fsdata_segment *segment; 137 | int len; 138 | bool use_gzip; 139 | 140 | if (find_file(path, &segment, &len, &use_gzip)) { 141 | web_server.send_error_code(200); 142 | web_server.send_last_modified("Wed, 21 Oct 2015 07:28:00 GMT"); 143 | web_server.send_content_type(web_server.get_mime_type_from_filename(path)); 144 | if (use_gzip) web_server.write("Content-Encoding: gzip\n"); 145 | web_server.end_headers(); 146 | 147 | while (segment && len > 0) { 148 | char *data = (char *)pgm_read_word(&(segment->data)); 149 | int i; 150 | int segment_len = min(len, httpd_fsdata_segment_len); 151 | 152 | while (segment_len > 0) { 153 | uint8_t buf[256]; 154 | int i; 155 | for (i = 0; i < segment_len && i < sizeof(buf); i++) { 156 | buf[i] = pgm_read_byte(data++); 157 | } 158 | segment_len -= i; 159 | len -= i; 160 | web_server.write(buf, i); 161 | } 162 | segment = (const struct httpd_fsdata_segment *)pgm_read_word(&(segment->next)); 163 | } 164 | } 165 | else { 166 | web_server.send_error_code(404); 167 | web_server.send_content_type("text/plain"); 168 | web_server.end_headers(); 169 | web_server.print("not found"); 170 | } 171 | 172 | return true; 173 | } 174 | 175 | static void file_uploader_handler(TinyWebServer& web_server, TinyWebPutHandler::PutAction action, char* buffer, int size) 176 | { 177 | static char *boundary = NULL; 178 | static size_t boundary_len = 0; 179 | static CircularBuffer cb; 180 | static enum state { search_begin=0, search_data, search_filename, in_filename, in_data} st; 181 | static int file_size = 0; 182 | static String fname = ""; 183 | const char *ct; 184 | 185 | switch (action) { 186 | case TinyWebPutHandler::START: 187 | { 188 | ct = web_server.get_header_value("Content-Type"); 189 | if (!ct) { 190 | Serial << F("Missing Content-Type\n"); 191 | break; 192 | } 193 | boundary = strstr(ct, "boundary="); 194 | if (!boundary) { 195 | Serial << F("Missing boundary= in '") << ct << "'\n"; 196 | break; 197 | } 198 | boundary += 5; 199 | boundary[0] = '\r'; 200 | boundary[1] = '\n'; 201 | boundary[2] = '-'; 202 | boundary[3] = '-'; 203 | boundary_len = strlen(boundary); 204 | D(Serial.print("**** Upload file ")); 205 | D(Serial.println(fname)); 206 | st = search_begin; 207 | file_size = 0; 208 | lduino.setStatus(F("Loading new ladder program")); 209 | break; 210 | } 211 | 212 | static char *fname_tag = "filename=\""; 213 | 214 | case TinyWebPutHandler::WRITE: 215 | for (int i = 0; i < size; i++) { 216 | D(Serial.write(*buffer)); 217 | 218 | switch(st) { 219 | case search_begin: 220 | cb.push(*buffer++); 221 | if (cb.remain() < boundary_len-2) break; 222 | if (cb.match(boundary+2, boundary_len-2)) { 223 | st = search_filename; 224 | D(Serial.println("search_filename")); 225 | cb.flush(); 226 | fname = ""; 227 | } 228 | else { 229 | cb.pop(); 230 | } 231 | break; 232 | case search_filename: 233 | cb.push(*buffer++); 234 | if (cb.remain() < strlen(fname_tag)) break; 235 | if (cb.match(fname_tag, strlen(fname_tag))) { 236 | st = in_filename; 237 | D(Serial.println("in_filename")); 238 | cb.flush(); 239 | } 240 | else { 241 | cb.pop(); 242 | } 243 | break; 244 | case in_filename: 245 | if (*buffer == '\"') { 246 | buffer++; 247 | st = search_data; 248 | D(Serial << F("fname=") << fname << "\n"); 249 | D(Serial.println("search_data")); 250 | } 251 | else { 252 | fname += *buffer++; 253 | } 254 | break; 255 | case search_data: 256 | cb.push(*buffer++); 257 | if (cb.remain() < 4) break; 258 | if (cb.match("\r\n\r\n", 4)) { 259 | st = in_data; 260 | D(Serial.println("in_data")); 261 | cb.flush(); 262 | } 263 | else { 264 | cb.pop(); 265 | } 266 | break; 267 | case in_data: 268 | cb.push(*buffer++); 269 | if (cb.remain() < boundary_len) break; 270 | if (cb.match(boundary, boundary_len)) { 271 | st = search_data; 272 | D(Serial.println("search_data")); 273 | cb.flush(); 274 | } 275 | else { 276 | if (file_size == 0) lduino.ClearProgram(); 277 | char c = cb.pop(); 278 | file_size++; 279 | lduino.LoadProgramLine(c); 280 | } 281 | break; 282 | }; 283 | } 284 | break; 285 | 286 | case TinyWebPutHandler::END: 287 | D(Serial.println("**** END")); 288 | D(Serial.print("file size ")); 289 | D(Serial.println(file_size)); 290 | Client& client = web_server.get_client(); 291 | String status; 292 | if (file_size > 0 && lduino.getProgramReady()) { 293 | status += fname; 294 | status += F(" loaded"); 295 | } 296 | else { 297 | status += F("Upload failure"); 298 | } 299 | Serial << status << '\n'; 300 | lduino.setStatus(status); 301 | //main_page(client, status); 302 | break; 303 | } 304 | } 305 | 306 | static boolean setconfig_handler(TinyWebServer& web_server) 307 | { 308 | const char* length_str = web_server.get_header_value("Content-Length"); 309 | int length = atoi(length_str); 310 | uint32_t start_time = millis(); 311 | StringParse buf; 312 | 313 | Client& client = web_server.get_client(); 314 | 315 | if (length <= 0) return true; 316 | 317 | while (buf.length() < length && client.connected() && (millis() - start_time < 30000)) { 318 | if (!client.available()) continue; 319 | buf += client.readString(); 320 | } 321 | 322 | IP_Config.ParseConfig(buf); 323 | IP_Config.SaveConfig(); 324 | 325 | return true; 326 | } 327 | 328 | static boolean reboot_handler(TinyWebServer& web_server) 329 | { 330 | web_server.send_error_code(200); 331 | web_server.send_content_type("text/plain"); 332 | web_server.end_headers(); 333 | web_server.write("rebooting"); 334 | doReset = true; 335 | return true; 336 | } 337 | 338 | static boolean getconfig_handler(TinyWebServer& web_server) 339 | { 340 | web_server.send_error_code(200); 341 | web_server.send_content_type("text/xml"); 342 | web_server.end_headers(); 343 | web_server << IP_Config.toXML(); 344 | return true; 345 | } 346 | 347 | void setup_PLC_Web() 348 | { 349 | TinyWebPutHandler::put_handler_fn = file_uploader_handler; 350 | web.begin(); 351 | } 352 | 353 | void poll_PLC_Web() 354 | { 355 | web.process(); 356 | } 357 | 358 | static void manage_setvalue(const char *path) 359 | { 360 | char *port = strstr(path, "&set="); 361 | if (!port) return; 362 | port += 5; 363 | 364 | char *value = strstr(path, "&value="); 365 | if (!value) return; 366 | value += 7; 367 | 368 | switch (*port) { 369 | case 'D': 370 | { 371 | int r = atoi(port + 1); 372 | if (r < 0 || r > 11) return; 373 | r += 2; 374 | Serial << "setPWM " << r << " " << value <<"\n"; 375 | lduino.setPWM(r, atoi(value)); 376 | break; 377 | } 378 | case 'A': 379 | { 380 | int r = atoi(port + 1); 381 | if (r < 0 || r > 11) return; 382 | r += 54; 383 | Serial << "setAnalog " << r << " " << value << "\n"; 384 | lduino.setAnalogInput(r, atoi(value)); 385 | break; 386 | } 387 | } 388 | } 389 | 390 | static void manage_toggle(const char *path) 391 | { 392 | char *toggle = strstr(path, "&toggle="); 393 | if (!toggle) return; 394 | toggle += 8; 395 | 396 | switch (*toggle) { 397 | case 'D': 398 | { 399 | int r = atoi(toggle + 1); 400 | if (r < 0 || r > 11) return; 401 | r += 2; 402 | Serial << "toggle " << r << "\n"; 403 | lduino.toggleDigitalOutput(r); 404 | break; 405 | } 406 | case 'R': 407 | { 408 | int r = atoi(toggle + 1); 409 | if (r < 0 || r > 9) return; 410 | r += 22; 411 | Serial << "toggle " << r << "\n"; 412 | lduino.toggleDigitalOutput(r); 413 | break; 414 | } 415 | case 'A': 416 | { 417 | int r = atoi(toggle + 1); 418 | if (r < 0 || r > 11) return; 419 | r += 54; 420 | Serial << "toggle " << r << "\n"; 421 | lduino.toggleDigitalInput(r); 422 | break; 423 | } 424 | case 'r': 425 | lduino.ToggleProgramRunning(); 426 | break; 427 | case 'i': 428 | lduino.ToggleIO_Polling(); 429 | break; 430 | default: 431 | break; 432 | } 433 | 434 | return; 435 | } 436 | 437 | static boolean getstate_handler(TinyWebServer& web_server) 438 | { 439 | web_server.send_error_code(200); 440 | web_server.send_content_type("text/xml"); 441 | web_server.send_content_type("Connection: keep-alive"); 442 | web_server.end_headers(); 443 | 444 | manage_toggle(web_server.get_path()); 445 | manage_setvalue(web_server.get_path()); 446 | Client& stream = web_server.get_client(); 447 | lduino.XML_State(stream); 448 | return true; 449 | } 450 | -------------------------------------------------------------------------------- /plcweb.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Frederic Rible 2 | // 3 | // This file is part of LDuino, an Arduino based PLC software compatible with LDmicro. 4 | // 5 | // LDuino is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // LDuino is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with LDmicro. If not, see . 17 | 18 | #ifndef _PLCWEB_h 19 | #define _PLCWEB_h 20 | 21 | #if defined(ARDUINO) && ARDUINO >= 100 22 | #include "arduino.h" 23 | #else 24 | #include "WProgram.h" 25 | #endif 26 | 27 | void setup_PLC_Web(); 28 | void poll_PLC_Web(); 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /sectionname.h: -------------------------------------------------------------------------------- 1 | #if defined(__AVR__) 2 | /* Copyright (c) 2009 Atmel Corporation 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in 13 | the documentation and/or other materials provided with the 14 | distribution. 15 | 16 | * Neither the name of the copyright holders nor the names of 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef __SECTIONNAME_H__ 34 | #define __SECTIONNAME_H__ 35 | 36 | /* Put all avr-libc functions in a common, unique sub-section name under .text. */ 37 | 38 | #define CLIB_SECTION .text.avr-libc 39 | #define MLIB_SECTION .text.avr-libc.fplib 40 | 41 | #define STR(x) _STR(x) 42 | #define _STR(x) #x 43 | 44 | #define ATTRIBUTE_CLIB_SECTION __attribute__ ((section (STR(CLIB_SECTION)))) 45 | #define ATTRIBUTE_MLIB_SECTION __attribute__ ((section (STR(MLIB_SECTION)))) 46 | 47 | #define ASSEMBLY_CLIB_SECTION .section CLIB_SECTION, "ax", @progbits 48 | #define ASSEMBLY_MLIB_SECTION .section MLIB_SECTION, "ax", @progbits 49 | 50 | #endif 51 | #endif 52 | -------------------------------------------------------------------------------- /stdlib_private.h: -------------------------------------------------------------------------------- 1 | #if defined(__AVR__) 2 | /* Copyright (c) 2004, Joerg Wunsch 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of the copyright holders nor the names of 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | /* $Id: stdlib_private.h 1657 2008-03-24 17:11:08Z arcanum $ */ 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #if !defined(__DOXYGEN__) 38 | 39 | struct __freelist { 40 | size_t sz; 41 | struct __freelist *nx; 42 | }; 43 | 44 | #endif 45 | 46 | extern char *__brkval; /* first location not yet allocated */ 47 | extern struct __freelist *__flp; /* freelist pointer (head of freelist) */ 48 | extern size_t __malloc_margin; /* user-changeable before the first malloc() */ 49 | extern char *__malloc_heap_start; 50 | extern char *__malloc_heap_end; 51 | 52 | extern char __heap_start; 53 | extern char __heap_end; 54 | 55 | /* Needed for definition of AVR_STACK_POINTER_REG. */ 56 | #include 57 | 58 | #define STACK_POINTER() ((char *)AVR_STACK_POINTER_REG) 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /sysinfo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "sysinfo.h" 3 | 4 | #define USE_RTOS 1 5 | #ifdef USE_RTOS 6 | #include 7 | #include 8 | #endif 9 | 10 | int sysinfo::freeRam() 11 | { 12 | extern int __heap_start, *__brkval; 13 | int v; 14 | return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval); 15 | } 16 | 17 | // Wipe RAM between heap and stack with 0x55 pattern 18 | 19 | void sysinfo::wipeRam() 20 | { 21 | extern int __heap_start, *__brkval; 22 | int v; 23 | for (byte *ptr = (byte *)(__brkval == 0 ? &__heap_start : __brkval); ptr < (byte *)&v; ptr++) { 24 | *ptr = 0x55; 25 | } 26 | } 27 | 28 | // Measure stack usage 29 | 30 | int sysinfo::stackUsage() 31 | { 32 | extern int __stack; 33 | extern int __heap_start, *__brkval; 34 | 35 | short *heap_top = (short *)(__brkval == 0 ? &__heap_start : __brkval); 36 | short *ptr = (short *)&__stack; 37 | 38 | int count = 0; 39 | while (*ptr != 0x5555 && ptr >= heap_top) { 40 | ptr--; 41 | count += 2; 42 | } 43 | 44 | return count; 45 | } 46 | 47 | // Measure unused Ram 48 | 49 | int sysinfo::unusedRam() 50 | { 51 | int count = 0; 52 | for (short *ptr = 0; (int)ptr <= RAMEND; ptr++) { 53 | if (*ptr == 0x5555) count += 2; 54 | } 55 | return count; 56 | } 57 | 58 | #ifdef USE_RTOS 59 | String sysinfo::DumpRTOS(void) 60 | { 61 | TaskStatus_t *pxTaskStatusArray; 62 | UBaseType_t uxArraySize = uxTaskGetNumberOfTasks(); 63 | String result; 64 | char line[32]; 65 | 66 | pxTaskStatusArray = (TaskStatus_t *)pvPortMalloc(uxArraySize * sizeof(TaskStatus_t)); 67 | if (pxTaskStatusArray == NULL) return ""; 68 | 69 | uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, NULL); 70 | 71 | result += F("Task |Free stack\n"); 72 | result += F("------------------------\n"); 73 | for (byte x = 0; x < uxArraySize; x++) 74 | { 75 | sprintf(line, "%-12s|%10d\n", pxTaskStatusArray[x].pcTaskName, pxTaskStatusArray[x].usStackHighWaterMark); 76 | result += line; 77 | } 78 | 79 | vPortFree(pxTaskStatusArray); 80 | return result; 81 | } 82 | #endif -------------------------------------------------------------------------------- /sysinfo.h: -------------------------------------------------------------------------------- 1 | class sysinfo { 2 | public: 3 | static void wipeRam(); // Wipe RAM between heap and stack with 0x55 pattern 4 | static int freeRam(); 5 | static int unusedRam(); 6 | static int stackUsage(); 7 | static String DumpRTOS(void); 8 | }; -------------------------------------------------------------------------------- /tools/makefsdata.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | #File coming from https://github.com/contiki-os/contiki/blob/f9a30236c87741259542210fc53340c39a7829ca/tools/makefsdata 3 | #Generate a .c source that preinitializes a file system 4 | #David Kopf July 2009 5 | #Extended from the existing non-coffee tool 6 | 7 | #The simplest format is used with httpd-fs.c. It contains a linked 8 | #list pointing to file names and file contents. 9 | 10 | #An extension uses the same linked list that points to a the file names 11 | #and contents within a coffee file system having fixed file sizes. 12 | #This allows the content to be rewritten by coffee while still being 13 | #readable by httpd-fs.c New files or sector extension of existing files 14 | #is not possible, but a larger initial sector allocation could be given 15 | #to some files to allow a limited size increase. Since this would leave the 16 | #wrong file size in the linked list, httpd-fs would have to terminate on 17 | #a reverse zero scan like coffee does. Rewriting the linked list would 18 | #probably be OK if in eeprom, but not if in program flash. Suggestions 19 | #for a workaround would be welcome! 20 | 21 | #Lastly a full coffee file system can be preinitialized. File reads must 22 | #then be done using coffee. 23 | 24 | #Assumes the coffee file_header structure is 25 | #struct file_header { 26 | # coffee_page_t log_page; 27 | # uint16_t log_records; 28 | # uint16_t log_record_size; 29 | # coffee_page_t max_pages; 30 | # uint8_t deprecated_eof_hint; 31 | # uint8_t flags=3 for the initial value; 32 | # char name[COFFEE_NAME_LENGTH]; 33 | # } __attribute__((packed)); 34 | 35 | use List::Util qw[min max]; 36 | 37 | goto DEFAULTS; 38 | START:$version="1.1"; 39 | 40 | #Process options 41 | for($n=0;$n<=$#ARGV;$n++) { 42 | $arg=$ARGV[$n]; 43 | if ($arg eq "-v") { 44 | print "makefsdata Version $version\n"; 45 | } elsif ($arg eq "-A") { 46 | $n++;$attribute=$ARGV[$n]; 47 | } elsif ($arg eq "-C") { 48 | $coffee=1; 49 | } elsif ($arg eq "-c") { 50 | $complement=1; 51 | } elsif ($arg eq "-i") { 52 | $n++;$includefile=$ARGV[$n]; 53 | # } elsif ($arg eq "-p") { 54 | # $n++;$coffee_page_length=$ARGV[$n]; 55 | } elsif ($arg eq "-s") { 56 | $n++;$coffee_sector_size=$ARGV[$n]; 57 | } elsif ($arg eq "-t") { 58 | $n++;$coffee_page_t =$ARGV[$n]; 59 | } elsif ($arg eq "-f") { 60 | $n++;$coffee_name_length=$ARGV[$n]; 61 | } elsif ($arg eq "-S") { 62 | $n++;$sectionname=$ARGV[$n]; 63 | } elsif ($arg eq "-l") { 64 | $linkedlist=1; 65 | } elsif ($arg eq "-d") { 66 | $n++;$directory=$ARGV[$n]; 67 | } elsif ($arg eq "-o") { 68 | $n++;$outputfile=$ARGV[$n]; 69 | $coffeefile=$outputfile; 70 | } else { 71 | DEFAULTS: 72 | #Set up defaults 73 | $coffee=0; 74 | #$coffee_page_length=256; 75 | $coffee_sector_size=256; 76 | $coffee_page_t=1; 77 | $coffee_name_length=16; 78 | $complement=0; 79 | $directory=""; 80 | $outputfile="httpd-fsdata.c"; 81 | $coffeefile="httpd-coffeedata.c"; 82 | $includefile="makefsdata.h"; 83 | $linkedlist=0; 84 | $attribute=""; 85 | $sectionname=".coffeefiles"; 86 | if (!$version) {goto START;} 87 | print "\n"; 88 | print "Usage: makefsdata <-d input_directory> <-o output_file>\n\n"; 89 | print " Generates c source file to pre-initialize a contiki file system.\n"; 90 | print " The default output is a simple linked list readable by httpd-fs.c\n"; 91 | print " and written to $outputfile.\n\n"; 92 | print " The -C option makes a coffee file system to default output $coffeefile.\n"; 93 | print " A linked list can be still be generated for use with httpd-fs.c so long\n"; 94 | print " as coffee does not extend, delete, or add any files.\n\n"; 95 | print " The input directory structure is copied. If input_directory is specified\n"; 96 | print " it becomes the root \"/\". If no input_directory is specified the first\n"; 97 | print " subdirectory found in the current directory is used as the root.\n\n"; 98 | print " WARNING : If the output file exists it will be overwritten without confirmation!\n\n"; 99 | print " Options are:\n"; 100 | print " -v Display the version number\n"; 101 | print " -A attribute Append \"attribute\" to the declaration, e.g. PROGMEM to put data in AVR program flash memory\n"; 102 | print " -C Use coffee file system format\n"; 103 | print " -c Complement the data, useful for obscurity or fast page erases for coffee\n"; 104 | print " -i filename Treat any input files with name \"filename\" as include files.\n"; 105 | print " Useful for giving a server a name and ip address associated with the web content.\n"; 106 | print " The default is $includefile.\n\n"; 107 | print " The following apply only to coffee file system\n"; 108 | # print " -p pagesize Page size in bytes (default $coffee_page_length)\n"; 109 | print " -s sectorsize Sector size in bytes (default $coffee_sector_size)\n"; 110 | print " -t page_t Number of bytes in coffee_page_t (1,2,or 4, default $coffee_page_t)\n"; 111 | print " -f namesize File name field size in bytes (default $coffee_name_length)\n"; 112 | print " -S section Section name for data (default $sectionname)\n"; 113 | print " -l Append a linked list for use with httpd-fs\n"; 114 | exit; 115 | } 116 | } 117 | 118 | #--------------------Configure parameters----------------------- 119 | if ($coffee) { 120 | $outputfile=$coffeefile; 121 | $coffee_header_length=2*$coffee_page_t+$coffee_name_length+6; 122 | if ($coffee_page_t==1) { 123 | $coffeemax=0xff; 124 | } elsif ($coffee_page_t==2) { 125 | $coffeemax=0xffff; 126 | } elsif ($coffee_page_t==4) { 127 | $coffeemax=0xffffffff; 128 | } else { 129 | die "Unsupported coffee_page_t $coffee_page_t\n"; 130 | } 131 | } else { 132 | # $coffee_page_length=1; 133 | $coffee_sector_size=1; 134 | $linkedlist=1; 135 | $coffee_name_length=256; 136 | $coffee_max=0xffffffff; 137 | $coffee_header_length=0; 138 | } 139 | $null="0x00";if ($complement) {$null="0xff";} 140 | $tab=" "; #optional tabs or spaces at beginning of line, e.g. "\t\t" 141 | 142 | #--------------------Create output file------------------------- 143 | #awkward but could not figure out how to compare paths later unless the file exists -- dak 144 | if (!open(OUTPUT, "> $outputfile")) {die "Aborted: Could not create output file $outputfile";} 145 | print(OUTPUT "\n"); 146 | close($outputfile); 147 | use Cwd qw(abs_path); 148 | if (!open(OUTPUT, "> $outputfile")) {die "Aborted: Could not create output file $outputfile";} 149 | $outputfile=abs_path($outputfile); 150 | 151 | #--------------------Get a list of input files------------------ 152 | if ($directory eq "") { 153 | opendir(DIR, "."); 154 | @files = grep { !/^\./ && !/(CVS|~)/ } readdir(DIR); 155 | closedir(DIR); 156 | foreach $file (@files) { 157 | if(-d $file && $file !~ /^\./) { 158 | $directory=$file; 159 | break; 160 | } 161 | } 162 | } 163 | if ($directory eq "") {die "Aborted: No subdirectory in current directory";} 164 | if (!chdir("$directory")) {die "Aborted: Directory \"$directory\" does not exist!";} 165 | 166 | if ($coffee) { 167 | print "Processing directory $directory as root of coffee file system\n"; 168 | } else { 169 | print "Processing directory $directory as root of packed httpd-fs file system\n"; 170 | } 171 | 172 | opendir(DIR, "."); 173 | @files = grep { !/^\./ && !/(CVS|~)/ && !/(ignore.txt)/ } readdir(DIR); 174 | closedir(DIR); 175 | 176 | foreach $file (@files) { 177 | if(-d $file && $file !~ /^\./) { 178 | print "Adding subdirectory $file\n"; 179 | opendir(DIR, $file); 180 | @newfiles = grep { !/^\./ && !/(CVS|~)/ } readdir(DIR); 181 | closedir(DIR); 182 | # printf "Adding files @newfiles\n"; 183 | @files = (@files, map { $_ = "$file/$_" } @newfiles); 184 | next; 185 | } 186 | } 187 | 188 | #--------------------Write the output file------------------- 189 | print "Writing to $outputfile\n"; 190 | ($DAY, $MONTH, $YEAR) = (localtime)[3,4,5]; 191 | printf(OUTPUT "/*********Generated by contiki/tools/makefsdata on %04d-%02d-%02d*********/\n", $YEAR+1900, $MONTH+1, $DAY); 192 | if ($coffee) { 193 | print(OUTPUT "/*For coffee filesystem of sector size $coffee_sector_size and header length $coffee_header_length bytes*/\n"); 194 | } 195 | print(OUTPUT "\n"); 196 | #--------------------Process include file------------------- 197 | foreach $file (@files) {if ($file eq $includefile) { 198 | open(FILE, $file) || die "Aborted: Could not open include file $file\n"; 199 | print "Including text from $file\n"; 200 | $file_length= -s FILE; 201 | read(FILE, $data, $file_length); 202 | print OUTPUT ($data); 203 | close(FILE); 204 | next; #include all include file matches 205 | # break; #include only first include file match 206 | }} 207 | 208 | #--------------------Process data files------------------- 209 | $n=0;$coffeesize=0;$coffeesectors=0; 210 | foreach $file (@files) {if(-f $file) { 211 | if (length($file)>=($coffee_name_length-1)) {die "Aborted: File name $file is too long";} 212 | if (abs_path("$file") eq abs_path("$outputfile")) { 213 | print "Skipping output file $outputfile - recursive input NOT allowed\n"; 214 | next; 215 | } 216 | if ($file eq $includefile) {next;} 217 | open(FILE, $file) || die "Aborted: Could not open file $file\n"; 218 | print "Adding /$file\n"; 219 | if (grep /.png/||/.jpg/||/jpeg/||/.pdf/||/.gif/||/.bin/||/.zip/,$file) {binmode FILE;} 220 | 221 | $file_length= -s FILE; 222 | $file =~ s-^-/-; 223 | $fvar = $file; 224 | $fvar =~ s-/-_-g; 225 | $fvar =~ s-\.-_-g; 226 | 227 | if ($coffee) { 228 | $coffee_sectors=int(($coffee_header_length+$file_length+$coffee_sector_size-1)/$coffee_sector_size); 229 | # $coffee_sectors=sprintf("%.0f",($coffee_header_length+$file_length+$coffee_sector_size-1)/$coffee_sector_size)-1; 230 | $coffee_length=$coffee_sectors*$coffee_sector_size; 231 | } else { 232 | $coffee_length=$file_length+length($file)+1; 233 | } 234 | $flen[$n]=$file_length; 235 | $clen[$n]=$coffee_length; 236 | $n++;$coffeesectors+=$coffee_sectors;$coffeesize+=$coffee_length; 237 | 238 | #--------------------Header----------------------------- 239 | #log_page 240 | if ($coffee) { 241 | print (OUTPUT " "); 242 | for($j=0;$j<$coffee_page_t;$j++) {print (OUTPUT "$ null ,");} 243 | #log_records, log_record_size 244 | for($j=0;$j<4;$j++) {print (OUTPUT "$null, ");} 245 | #max_pages 246 | if ($complement) {$coffee_sectors=$coffee_sectors^0xffffffff;} 247 | if ($coffee_page_t==1) { 248 | printf(OUTPUT "0x%2.2x, ",($coffee_sectors )&0xff); 249 | } elsif ($coffee_page_t==2) { 250 | printf(OUTPUT "0x%2.2x, ",($coffee_sectors>> 8)&0xff); 251 | printf(OUTPUT "0x%2.2x, ",($coffee_sectors )&0xff); 252 | } elsif ($coffee_page_t==4) { 253 | printf(OUTPUT "0x%2.2x, ",($coffee_sectors>>24)&0xff); 254 | printf(OUTPUT "0x%2.2x, ",($coffee_sectors>>16)&0xff); 255 | printf(OUTPUT "0x%2.2x, ",($coffee_sectors>> 8)&0xff); 256 | printf(OUTPUT "0x%2.2x, ",($coffee_sectors )&0xff); 257 | } 258 | if ($complement) {$coffee_sectors=$coffee_sectors^0xffffffff;} 259 | #eof hint and flags 260 | if ($complement) { 261 | print(OUTPUT "0xff, 0xfc,\n$tab"); 262 | } else { 263 | print(OUTPUT "0x00, 0x03,\n$tab"); 264 | } 265 | } 266 | 267 | #-------------------File name-------------------------- 268 | printf(OUTPUT "const char name%s[%d] $attribute = \"$file\";\n", $fvar, length($file)+1); 269 | 270 | #------------------File Data--------------------------- 271 | $coffee_length-=$coffee_header_length; 272 | $i = 0; 273 | $cnt = 0; 274 | $section = 0; 275 | $maxsize = 30000; 276 | 277 | while(read(FILE, $data, 1)) { 278 | $temp=unpack("C", $data); 279 | if ($complement) {$temp=$temp^0xff;} 280 | 281 | if ($cnt == $maxsize) { 282 | $i = 0; 283 | $cnt = 0; 284 | $section++; 285 | print(OUTPUT " };\n"); 286 | } 287 | 288 | if ($cnt == 0) { 289 | $section_size = min($coffee_length, $maxsize); 290 | printf(OUTPUT "const char data" . $section . $fvar . "[$section_size] $attribute = {\n$tab 0x%2.2x", $temp); 291 | } 292 | else { 293 | if($i == 10) { 294 | printf(OUTPUT ",\n$tab 0x%2.2x", $temp); 295 | $i = 0; 296 | } else { 297 | printf(OUTPUT ", 0x%2.2x", $temp); 298 | } 299 | } 300 | $i++; 301 | $coffee_length--; 302 | $cnt++; 303 | } 304 | 305 | if ($coffee) { 306 | print (OUTPUT ","); 307 | while (--$coffee_length) { 308 | if($i==9) { 309 | print(OUTPUT " $null,\n$tab"); 310 | $i = 0; 311 | } else { 312 | print (OUTPUT " $null,"); 313 | $i++; 314 | } 315 | } 316 | print (OUTPUT " $null"); 317 | } 318 | print (OUTPUT "$tab};\n\n"); 319 | close(FILE); 320 | push(@fvars, $fvar); 321 | push(@pfiles, $file); 322 | }} 323 | 324 | if ($linkedlist) { 325 | #-------------------httpd_fsdata_file links------------------- 326 | #The non-coffee PROGMEM flash file system for the Raven webserver uses a linked flash list as follows: 327 | print(OUTPUT "\n\n/* Structure of linked list (all offsets relative to start of section):*/\n"); 328 | print(OUTPUT "struct httpd_fsdata_file {\n"); 329 | print(OUTPUT "$tab const struct httpd_fsdata_file *next; //actual flash address of next link\n"); 330 | print(OUTPUT "$tab const char *name; //offset to coffee file name\n"); 331 | print(OUTPUT "$tab const char *data; //offset to coffee file data\n"); 332 | print(OUTPUT "$tab const long len; //length of file data\n"); 333 | print(OUTPUT "#if HTTPD_FS_STATISTICS == 1 //not enabled since list is in PROGMEM\n"); 334 | print(OUTPUT "$tab uint16_t count; //storage for file statistics\n"); 335 | print(OUTPUT "#endif\n"); 336 | print(OUTPUT "};\n\n"); 337 | 338 | # For the static httpd-fs.c file system the file name and data addresses in the linked list 339 | # point to the actual memory locations. 340 | # For the coffee file system the addresses start from zero. The starting address must be added 341 | # in the coffee read routine. 342 | 343 | for($i = 0; $i < @fvars; $i++) { 344 | $file = $pfiles[$i]; 345 | $fvar = $fvars[$i]; 346 | if($i == 0) { 347 | $prevfile = "NULL"; 348 | $data_offset=0; 349 | } else { 350 | $data_offset=$data_offset+$clen[$i-1]; 351 | $prevfile = "file" . $fvars[$i - 1]; 352 | } 353 | $filename_offset=$data_offset+6+2*$coffee_page_t; 354 | 355 | print(OUTPUT "const struct httpd_fsdata_file"); 356 | for ($t=length($file);$t<16;$t++) {print(OUTPUT " ")}; 357 | print(OUTPUT " file".$fvar."[] "); 358 | if ($attribute) {print(OUTPUT "$attribute ");} 359 | print(OUTPUT "={{"); 360 | for ($t=length($prevfile);$t<20;$t++) {print(OUTPUT " ")}; 361 | print(OUTPUT "$prevfile, "); 362 | if ($coffee) { 363 | $coffee_offset=$data_offset+$coffee_header_length; 364 | if ($coffee_offset>0xffff) {print "Warning : Linked list offset field overflow\n";} 365 | printf(OUTPUT "(const char *)0x%4.4x, ",$filename_offset); 366 | printf(OUTPUT "(const char *)0x%4.4x, ",$coffee_offset); 367 | printf(OUTPUT "%5u}};\n",$flen[$i]); 368 | } else { 369 | print(OUTPUT "name$fvar"); 370 | print(OUTPUT ", data0$fvar"); 371 | print(OUTPUT ", $clen[$i]"); 372 | print(OUTPUT " }};\n"); 373 | } 374 | } 375 | print(OUTPUT "\n#define HTTPD_FS_ROOT file$fvars[$n-1]\n"); 376 | print(OUTPUT "#define HTTPD_FS_NUMFILES $n\n"); 377 | print(OUTPUT "#define HTTPD_FS_SIZE $coffeesize\n"); 378 | } 379 | print "All done, files occupy $coffeesize bytes\n"; 380 | 381 | -------------------------------------------------------------------------------- /tools/makefsdata.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import fnmatch 4 | import binascii 5 | import gzip 6 | 7 | declaration=""" 8 | struct httpd_fsdata_file { 9 | const char *name; 10 | const int len; 11 | const boolean gzip; 12 | const struct httpd_fsdata_segment *first; 13 | const struct httpd_fsdata_file *next; 14 | }; 15 | 16 | struct httpd_fsdata_segment { 17 | const char *data; 18 | const struct httpd_fsdata_segment *next; 19 | }; 20 | 21 | """ 22 | 23 | symbols = [] 24 | segment_len = 30000 25 | use_gzip = True 26 | 27 | def GenerateStructures(output): 28 | global symbols 29 | next_symbol = "NULL" 30 | for (symbol, segment, len) in symbols: 31 | next_segment = "NULL" 32 | for s in range(segment,-1,-1): 33 | 34 | output.write("static const struct httpd_fsdata_segment %s_segment%d PROGMEM = {%s_data%d, %s};\n" 35 | % (symbol, s, symbol, s, next_segment)) 36 | next_segment = "&%s_segment%d" % (symbol, s) 37 | output.write("static const struct httpd_fsdata_file %s_file PROGMEM = {%s_name, %d, %s, &%s_segment0, %s};\n" 38 | % (symbol, symbol, len, "true", symbol, next_symbol)) 39 | next_symbol = "&" + symbol + "_file" 40 | 41 | 42 | def GetFilesList(rootdir): 43 | # Read list of file patterns to ignore 44 | fo = open(rootdir + "/ignore.txt", "r") 45 | ignore = fo.readlines() 46 | fo.close() 47 | ignore = [os.path.normpath(s)[:-1] for s in ignore] # remove LF 48 | 49 | # Get the list of files 50 | file_list = [] 51 | for root, subdirs, files in os.walk(rootdir): 52 | for file in files: 53 | filePath = os.path.join(root,file) 54 | filePath = os.path.relpath(filePath, rootdir) 55 | skip = False 56 | for pattern in ignore: 57 | if fnmatch.fnmatch(filePath, os.path.join(pattern)): 58 | skip = True 59 | print("SKIP " + filePath) 60 | if (not skip): 61 | file_list.append(filePath) 62 | return file_list 63 | 64 | def EncodeFile(output, rootdir, file, use_gzip=False): 65 | global symbols 66 | fname = file.replace("\\", "/") 67 | symbol = fname.replace("/", "_") 68 | symbol = symbol.replace(".", "_") 69 | output.write("static const char %s_name[] PROGMEM = \"/%s\";\n" % (symbol, fname)) 70 | file = os.path.join(rootdir, file) 71 | 72 | fi = open(file, "rb") 73 | file_content = fi.read() 74 | fi.close() 75 | 76 | if use_gzip: 77 | file_content = gzip.compress(file_content) 78 | 79 | length = len(file_content) 80 | segment = 0 81 | cnt = 0 82 | 83 | for index in range(0, length): 84 | if cnt == segment_len: 85 | output.write("\n};\n") 86 | segment += 1 87 | cnt = 0 88 | if cnt == 0: 89 | output.write("static const char %s_data%d[] PROGMEM = {\n" % (symbol, segment)) 90 | h = "0x%02X" % (file_content[index]) 91 | if cnt == 0: 92 | output.write("\t" + h) 93 | else: 94 | if (cnt % 10) == 0: 95 | output.write(",\n\t" + h) 96 | else: 97 | output.write(", " + h) 98 | cnt += 1 99 | 100 | symbols.append((symbol, segment, length)) 101 | output.write("\n};\n\n") 102 | 103 | rootdir = "web" 104 | fo = open("httpd-fsdata.h", "w") 105 | file_list = GetFilesList(rootdir) 106 | print(file_list) 107 | 108 | for file in file_list: 109 | EncodeFile(fo, rootdir, file, use_gzip) 110 | 111 | fo.write(declaration) 112 | GenerateStructures(fo) 113 | fo.write("\nconst int httpd_fsdata_segment_len = %d;\n" % (segment_len)) 114 | (root, segment, len) = symbols[-1] 115 | fo.write("const httpd_fsdata_file *httpd_fsroot = &%s_file;\n" % (root)) 116 | fo.close() -------------------------------------------------------------------------------- /web/animated_status.css: -------------------------------------------------------------------------------- 1 | /* 2 | http://codepen.io/amustill/pen/dgjxE 3 | */ 4 | .loading 5 | { 6 | display: inline-block; 7 | border-width: 10px; 8 | border-radius: 50%; 9 | -webkit-animation: spin 1s linear infinite; 10 | -moz-animation: spin 1s linear infinite; 11 | -o-animation: spin 1s linear infinite; 12 | animation: spin 1s linear infinite; 13 | } 14 | .style-1 15 | { 16 | border-style: solid; 17 | border-color: #1AAF56 transparent; 18 | } 19 | @-webkit-keyframes spin 20 | { 21 | 100% 22 | { 23 | -webkit-transform: rotate(359deg); 24 | } 25 | } 26 | @-moz-keyframes spin 27 | { 28 | 100% 29 | { 30 | -moz-transform: rotate(359deg); 31 | } 32 | } 33 | #traffic-indicator 34 | { 35 | /*+placement: 15px 0px;*/ 36 | position: relative; 37 | left: 15px; 38 | top: 0px; 39 | } 40 | @-o-keyframes spin 41 | { 42 | 100% 43 | { 44 | -moz-transform: rotate(359deg); 45 | } 46 | } 47 | @keyframes spin 48 | { 49 | 100% 50 | { 51 | transform: rotate(359deg); 52 | } 53 | } 54 | .paused 55 | { 56 | -webkit-animation-play-state: paused; 57 | -moz-animation-play-state: paused; 58 | -o-animation-play-state: paused; 59 | animation-play-state: paused; 60 | border-color: #EB380D transparent; 61 | } 62 | -------------------------------------------------------------------------------- /web/config.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | LDuino Config 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 |
16 |
17 |
21 | 22 |
25 |
26 |
27 |
28 | 29 | 30 | 33 | 34 | 35 | 38 | 39 |
31 | 32 |
36 | 37 |
40 |
41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /web/config.js: -------------------------------------------------------------------------------- 1 | var fields = [ 2 | ['useDHCP', 'Use DHCP', 'checkbox'], 3 | ['mac', 'MAC', 'text'], 4 | ['ip', 'IP', 'text'], 5 | ['subnet', 'Subnet', 'text'], 6 | ['gateway', 'Gateway', 'text'], 7 | ['dns', 'DNS', 'text'], 8 | ['modbus_baudrate', 'Modbus Baudrate', 'text'], 9 | ]; 10 | 11 | function addField(id, name, t) 12 | { 13 | var tbl = $("#config_table"); 14 | 15 | html = '' + name + ':'; 16 | html += ''; 17 | tbl.append(html); 18 | } 19 | 20 | function init() { 21 | console.log("init called"); 22 | 23 | for (i = 0; i < fields.length; i++) { 24 | f = fields[i]; 25 | addField(f[0], f[1], f[2]); 26 | } 27 | 28 | var request = new XMLHttpRequest(); 29 | request.onreadystatechange = function () { 30 | if (this.readyState == XMLHttpRequest.DONE) { 31 | if (this.responseXML != null) { 32 | var v = this.responseXML.getElementsByTagName('config'); 33 | v = v[0].childNodes; 34 | var nb = v.length; 35 | for (count = 0; count < nb; count++) { 36 | var id = v[count].nodeName; 37 | if (id == '#text') continue; 38 | var f = $('#' + id); 39 | if (!f) continue; 40 | f = f[0]; 41 | if (f.type == "checkbox") { 42 | f.checked = (v[count].textContent == "1"); 43 | } 44 | else { 45 | f.value = v[count].textContent; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | request.open("GET", "getconfig.xml", true); 52 | request.timeout = 5000; 53 | request.send(null); 54 | } 55 | 56 | function setconfig() { 57 | document.getElementById("form_1").submit(); 58 | } -------------------------------------------------------------------------------- /web/getconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1DE:AD:BE:EF:FE:ED192.168.1.229255.255.255.0192.168.1.1192.168.1.19600 4 | -------------------------------------------------------------------------------- /web/getstate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1195040967213828101620940<_status>Booting 4 | 0:0:1,1:0:1,2:0:1,3:0:1,4:0:0,5:0:0,6:0:0,7:0:0,8:1:0,9:0:0,10:0:0,11:0:0 5 | 0:0:1,1:0:1,2:0:0,3:0:0,4:0:0,5:0:0,6:0:0,7:0:0,8:0:0,9:0:1 6 | 0:0:1,1:0:1,2:0:1,3:0:1,4:1:0,5:1:1,6:0:1,7:0:1,8:0:0,9:0:0 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/ignore.txt: -------------------------------------------------------------------------------- 1 | wpscripts/jquery* 2 | getconfig.xml 3 | getstate.xml 4 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LDuino 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 22 | 23 | 24 | 26 | 28 | 30 | 103 | 104 | 105 | 107 | 109 | 111 | 114 | 115 | 116 |
LDuino - © 2016-2018 Frederic 21 | RIBLE (f1oat@f1oat.org)

25 |

27 |

29 |
31 | 32 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | 48 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 93 | 94 | 95 | 96 | 99 | 100 | 101 |
Program:
Physical I/O:
Opcodes:
EEPROM: / 62 |
I/O / vars: 67 | / 68 |
Cycle: ms
Processing time: µs
Free Ram:
Status:
Refreshing: 97 |
98 |
102 |

106 |

108 |

110 |
112 | 113 |
117 |
118 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /web/lduino.js: -------------------------------------------------------------------------------- 1 | function reboot() { 2 | if (!confirm("Confirm boot?")) return; 3 | var request = new XMLHttpRequest(); 4 | request.open("GET", "reboot", true); 5 | request.timeout = 5000; 6 | request.send(null); 7 | } 8 | 9 | function popup(url) { 10 | newwindow = window.open(url, 'name', 'height=400,width=400,menubar=false,resizable=false,scrollbars=false, status=false, titlebar=false, toolbar=false'); 11 | if (window.focus) { newwindow.focus() } 12 | return false; 13 | } 14 | 15 | var strToggle = ""; 16 | var strSetPWM= ""; 17 | var statusOK = false; 18 | 19 | function ToggleTrafficIndicator(a) 20 | { 21 | if (a) $("#traffic-indicator").removeClass("paused"); 22 | else $("#traffic-indicator").addClass("paused"); 23 | } 24 | 25 | function setLED(id, v, mapped) { 26 | $("#" + id + "_led").prop("checked", v ? true : false); 27 | $("#" + id + "_label").attr("mapped", mapped ? true : false); 28 | $("#" + id + "_led").show(); 29 | $("#" + id + "_led2").show(); 30 | $("#" + id + "_lcd").hide(); 31 | //var f = document.getElementById(id); 32 | //if (f) f.style.opacity = value ? 1 : 0; 33 | //else console.log("setLED(" + id + ") not found"); 34 | } 35 | 36 | function setLCD(id, v) { 37 | $("#" + id + "_lcd").val(v); 38 | $("#" + id + "_lcd").show(); 39 | $("#" + id + "_led").hide(); 40 | $("#" + id + "_led2").hide(); 41 | } 42 | 43 | function resetLED(id) { 44 | setLED(id, false); 45 | var f = $("#" + id + "_led").get(0); 46 | f.onclick = function () { Toggle(this) }; 47 | } 48 | 49 | function resetLCD(id) { 50 | var f = $("#" + id + "_lcd").get(0); 51 | f.onclick = function () { PromptPWM(this) }; 52 | } 53 | 54 | function createPanelLine(id1, id2) { 55 | var tr = document.createElement("tr"); 56 | tr.appendChild(createLabel(id1)); 57 | tr.appendChild(createLCD(id1)); 58 | tr.appendChild(createLCD(id2)); 59 | tr.appendChild(createLabel(id2)); 60 | return tr; 61 | } 62 | 63 | function createIO(id) 64 | { 65 | var html = ""; 66 | html += ""; 67 | html += ""; 68 | html += ""; 69 | html += ""; 70 | html += ""; 71 | 72 | return html; 73 | } 74 | 75 | function createSubPanel(col, prefix, nb, title) { 76 | var html = ""; 77 | html += ""; 78 | for (i = 0; i < nb / 2; i++) { 79 | var id1 = prefix + i; 80 | var id2 = prefix + (i + nb / 2); 81 | if (id2 == "A10") id2 = "IN0"; 82 | if (id2 == "A11") id2 = "IN1"; 83 | html += "" 84 | html += ""; 85 | html += createIO(id1); 86 | html += createIO(id2); 87 | html += ""; 88 | html += ""; 89 | } 90 | html += "
" + title + "
" + id1 + "" + id2 + "
"; 91 | cell = $("#io_" + col); 92 | cell.html(html); 93 | } 94 | 95 | function createPanel() { 96 | createSubPanel(0, "A", 12, "Inputs"); 97 | createSubPanel(1, "D", 12, "Outputs"); 98 | createSubPanel(2, "R", 10, "Relays"); 99 | } 100 | 101 | function init() { 102 | createPanel(); 103 | 104 | for (i = 0; i <= 11; i++) { 105 | resetLED('D' + i); 106 | resetLCD('D' + i); 107 | } 108 | 109 | for (i = 0; i <= 9; i++) { 110 | resetLED('A' + i); 111 | resetLCD('A' + i); 112 | } 113 | 114 | for (i = 0; i <= 9; i++) { 115 | resetLED('R' + i); 116 | } 117 | 118 | resetLED('IN0'); 119 | resetLED('IN1'); 120 | resetLED('running'); 121 | resetLED('io_polling'); 122 | setLCD("A0", 1001); 123 | GetArduinoIO(); 124 | } 125 | 126 | function updateLEDs(xml, tag, prefix) { 127 | var pins = xml.getElementsByTagName(tag)[0].firstChild; 128 | if (!pins) return; 129 | var plist = pins.nodeValue.split(","); 130 | var nb = plist.length; 131 | for (count = 0; count < nb; count++) { 132 | var x = plist[count].split(":"); 133 | setLED(prefix + x[0], x[1] > 0, parseInt(x[2])); 134 | } 135 | } 136 | 137 | function updateLCDs(xml, tag, prefix) { 138 | var pins = xml.getElementsByTagName(tag)[0].firstChild; 139 | if (!pins) return; 140 | var plist = pins.nodeValue.split(","); 141 | var nb = plist.length; 142 | for (count = 0; count < nb; count++) { 143 | var x = plist[count].split(":"); 144 | setLCD(prefix + x[0], x[1]);; 145 | } 146 | } 147 | 148 | function GetArduinoIO() { 149 | nocache = "&nocache=" + Math.random() * 1000000; 150 | var request = new XMLHttpRequest(); 151 | 152 | if (!statusOK) ToggleTrafficIndicator(false); 153 | 154 | request.onreadystatechange = function () { 155 | if (this.readyState != XMLHttpRequest.DONE) return; 156 | setTimeout('GetArduinoIO()', 500); 157 | if (this.status == 200) { 158 | if (this.responseXML != null) { 159 | statusOK = true; 160 | ToggleTrafficIndicator(true); 161 | document.body.style.background = "white"; 162 | 163 | // XML file received - contains analog values, switch values and LED states 164 | var count; 165 | 166 | // IO pins state 167 | updateLEDs(this.responseXML, "outputs", "D"); 168 | updateLEDs(this.responseXML, "relays", "R"); 169 | updateLEDs(this.responseXML, "inputs", "A"); 170 | updateLCDs(this.responseXML, "analog", "A") 171 | updateLCDs(this.responseXML, "pwm", "D") 172 | 173 | // state 174 | var v = this.responseXML.getElementsByTagName('state'); 175 | v = v[0].childNodes; 176 | var nb = v.length; 177 | for (count = 0; count < nb; count++) { 178 | var n = v[count].nodeName; 179 | if (n == '#text') continue; 180 | //if (n == strToggle) continue; 181 | if (n == "running" || n == "io_polling") { 182 | setLED(n, (v[count].textContent == "1")); 183 | } 184 | else { 185 | $("#" + n).val(v[count].textContent); 186 | } 187 | } 188 | } 189 | } 190 | } 191 | 192 | var params = ""; 193 | if (strToggle != "") { 194 | params += "&toggle=" + strToggle; 195 | } 196 | if (strSetPWM != "") { 197 | params += strSetPWM; 198 | } 199 | request.open("GET", "getstate.xml" + params, true); 200 | request.timeout = 5000; 201 | request.send(null); 202 | 203 | statusOK = false; 204 | strToggle = ""; 205 | strSetPWM = ""; 206 | } 207 | 208 | function Toggle(src) { 209 | src.checked = !src.checked; // State refresh will come from the PLC 210 | strToggle = src.id.replace("_led", ""); 211 | console.log("Toggle " + strToggle); 212 | } 213 | 214 | function PromptPWM(src) 215 | { 216 | var modal = $('#setPWM_modal').get(0); 217 | var oldvalue = parseInt(src.value); 218 | $("#pwm_value").val(oldvalue); 219 | $("#pwm_range").val(oldvalue); 220 | modal.style.display = "block"; 221 | 222 | window.onclick = function (event) { 223 | if (event.target == modal) { 224 | modal.style.display = "none"; 225 | } 226 | } 227 | 228 | $("#pwm_ok").get(0).onclick = function () { 229 | var newvalue = $("#pwm_value").val(); 230 | strSetPWM = "&setPWM=" + src.id.replace("_lcd", "") + "&value=" + newvalue; 231 | console.log("SetPWM " + strSetPWM); 232 | } 233 | } 234 | 235 | function configPin(src) 236 | { 237 | console.log("Config pin " + src.innerText); 238 | } -------------------------------------------------------------------------------- /web/led.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f1oat/LDuino/d054cdcccded12e4b40e457f0288768c55a742d7/web/led.png -------------------------------------------------------------------------------- /web/range.css: -------------------------------------------------------------------------------- 1 | INPUT[type="range"] { 2 | width: 100%; 3 | margin-top: 7.3px; 4 | margin-right: 0px; 5 | margin-bottom: 7.3px; 6 | margin-left: 0px; 7 | } 8 | 9 | INPUT[type="range"]:focus { 10 | outline-width: medium; 11 | outline-style: none; 12 | outline-color: -moz-initial; 13 | } 14 | 15 | INPUT[type="range"]::-moz-range-track { 16 | width: 100%; 17 | height: 8.4px; 18 | cursor: pointer; 19 | box-shadow: 0px 0px 0.1px black, 0px 0px 0px #0d0d0d; 20 | background-color: #2d89c6; 21 | background-image: none; 22 | background-repeat: repeat; 23 | background-attachment: scroll; 24 | background-position: 0% 0%; 25 | border-radius: 1.3px; 26 | border-top-width: 0.2px; 27 | border-top-style: solid; 28 | border-top-color: #010101; 29 | border-right-width: 0.2px; 30 | border-right-style: solid; 31 | border-right-color: #010101; 32 | border-bottom-width: 0.2px; 33 | border-bottom-style: solid; 34 | border-bottom-color: #010101; 35 | border-left-width: 0.2px; 36 | border-left-style: solid; 37 | border-left-color: #010101; 38 | } 39 | 40 | #pwm_value { 41 | font-family: arial; 42 | font-weight: bold; 43 | border-top-width: 1px; 44 | border-top-style: solid; 45 | border-top-color: #2d89c6; 46 | border-right-width: 1px; 47 | border-right-style: solid; 48 | border-right-color: #2d89c6; 49 | border-bottom-width: 1px; 50 | border-bottom-style: solid; 51 | border-bottom-color: #2d89c6; 52 | border-left-width: 1px; 53 | border-left-style: solid; 54 | border-left-color: #2d89c6; 55 | padding-top: 4px; 56 | padding-right: 4px; 57 | padding-bottom: 4px; 58 | padding-left: 4px; 59 | border-radius: 5px; 60 | font-size: 15px; 61 | } 62 | 63 | INPUT[type="range"]::-moz-range-thumb { 64 | box-shadow: 1px 1px 1px black, 0px 0px 1px #0d0d0d; 65 | border-top-width: 1px; 66 | border-top-style: solid; 67 | border-top-color: #810000; 68 | border-right-width: 1px; 69 | border-right-style: solid; 70 | border-right-color: #810000; 71 | border-bottom-width: 1px; 72 | border-bottom-style: solid; 73 | border-bottom-color: #810000; 74 | border-left-width: 1px; 75 | border-left-style: solid; 76 | border-left-color: #810000; 77 | height: 23px; 78 | width: 7px; 79 | border-radius: 3px; 80 | background-color: rgba(154, 161, 211, 0.79); 81 | background-image: none; 82 | background-repeat: repeat; 83 | background-attachment: scroll; 84 | background-position: 0% 0%; 85 | cursor: pointer; 86 | } 87 | 88 | #reboot { 89 | background-color: #1d812a; 90 | } 91 | 92 | -------------------------------------------------------------------------------- /web/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | word-wrap: break-word; 5 | } 6 | 7 | img { 8 | border: none; 9 | } 10 | 11 | /*INPUT 12 | { 13 | }*/ 14 | input#datafile { 15 | border: 1px solid black; 16 | height: 22px; 17 | width: 360px; 18 | font-weight: normal; 19 | background-color: #DEECFE; 20 | margin: 8px; 21 | padding: 5px; 22 | } 23 | 24 | input[type=checkbox] { 25 | height: 16px !important; 26 | width: 16px !important; 27 | } 28 | 29 | textarea { 30 | border: 1px solid black; 31 | padding: 0; 32 | } 33 | 34 | * { 35 | box-sizing: content-box; 36 | -moz-box-sizing: content-box; 37 | -webkit-box-sizing: content-box; 38 | -ms-box-sizing: content-box; 39 | font-family: arial; 40 | } 41 | 42 | a:link { 43 | color: #393939; 44 | text-decoration: none; 45 | } 46 | 47 | a:visited { 48 | color: #393939; 49 | text-decoration: none; 50 | } 51 | 52 | td .field { 53 | text-align: left; 54 | } 55 | 56 | a:hover { 57 | color: #393939; 58 | text-decoration: none; 59 | } 60 | 61 | a:active { 62 | color: #393939; 63 | text-decoration: none; 64 | } 65 | 66 | .LCD { 67 | background: #F4F9B8; 68 | font-family: Courier New; 69 | text-align: center; 70 | vertical-align: middle; 71 | font-size: 12px; 72 | color: #000000; 73 | font-weight: bold; 74 | width: 30px; 75 | border: 1px solid black; 76 | /*+border-radius: 5px;*/ 77 | -moz-border-radius: 5px; 78 | -webkit-border-radius: 5px; 79 | -khtml-border-radius: 5px; 80 | border-radius: 5px; 81 | padding: 2px; 82 | } 83 | 84 | .LCD:active { 85 | filter: brightness(120%); 86 | } 87 | 88 | .STATUS { 89 | width: 30px; 90 | text-align: right; 91 | padding: 0; 92 | border: 0px solid black; 93 | } 94 | 95 | table { 96 | width: 100%; 97 | text-align: center; 98 | } 99 | 100 | .LABEL { 101 | background: #FFFFFF; 102 | font-family: Courier New; 103 | text-align: center; 104 | vertical-align: middle; 105 | font-size: 20px; 106 | color: #000000; 107 | font-weight: bold; 108 | } 109 | 110 | .LABEL[mapped=false] { 111 | color: rgba(149, 149, 149, 0.610); 112 | font-weight: lighter; 113 | } 114 | 115 | .TITLE { 116 | font-weight: bold; 117 | font-size: 20px; 118 | padding-bottom: 6px; 119 | font-family: Courier New; 120 | text-align: center; 121 | } 122 | 123 | .col1 { 124 | font-weight: bold; 125 | text-align: right; 126 | } 127 | 128 | .col2 { 129 | text-align: left; 130 | font-weight: normal; 131 | } 132 | 133 | input[type=checkbox].LED { 134 | position: absolute; 135 | z-index: -1000; 136 | left: -1000px; 137 | overflow: hidden; 138 | clip: rect(0 0 0 0); 139 | height: 1px; 140 | width: 1px; 141 | margin: -1px; 142 | padding: 0; 143 | border: 0; 144 | } 145 | 146 | input[type=checkbox].LED + label.LED { 147 | padding-left: 21px; 148 | height: 16px; 149 | display: inline-block; 150 | line-height: 16px; 151 | background-repeat: no-repeat; 152 | background-position: 0 0; 153 | font-size: 16px; 154 | vertical-align: middle; 155 | cursor: pointer; 156 | } 157 | 158 | input[type=checkbox].LED:checked + label.LED { 159 | background-position: 0 -16px; 160 | } 161 | 162 | label.LED { 163 | background-image: url('led.png'); 164 | -webkit-touch-callout: none; 165 | -webkit-user-select: none; 166 | -khtml-user-select: none; 167 | -moz-user-select: none; 168 | -ms-user-select: none; 169 | user-select: none; 170 | } 171 | 172 | .panel { 173 | /*+border-radius: 6px;*/ 174 | -moz-border-radius: 6px; 175 | -webkit-border-radius: 6px; 176 | -khtml-border-radius: 6px; 177 | border-radius: 6px; 178 | vertical-align: top; 179 | padding: 1px 4px; 180 | border: 2px solid #000000; 181 | text-shadow: 0 0; 182 | } 183 | 184 | .panel td { 185 | vertical-align: middle !important; 186 | } 187 | 188 | #gtbl td { 189 | vertical-align: top; 190 | } 191 | 192 | .C-1 { 193 | line-height: 23.00px; 194 | font-family: "Comic Sans MS", cursive; 195 | font-style: normal; 196 | font-weight: bold; 197 | color: #000000; 198 | background-color: transparent; 199 | text-decoration: none; 200 | font-variant: normal; 201 | font-size: 16.0px; 202 | vertical-align: 0; 203 | } 204 | 205 | td .label { 206 | text-align: right; 207 | line-height: 20.00px; 208 | font-family: "Arial", sans-serif; 209 | font-style: normal; 210 | font-weight: 700; 211 | color: #000000; 212 | background-color: transparent; 213 | text-decoration: none; 214 | font-variant: normal; 215 | font-size: 16.0px; 216 | vertical-align: 0; 217 | } 218 | 219 | button, input[type="submit"] { 220 | background: #3498DB; 221 | background-image: -webkit-linear-gradient(top, #3498DB, #2980B9); 222 | background-image: -moz-linear-gradient(top, #3498DB, #2980B9); 223 | background-image: -ms-linear-gradient(top, #3498DB, #2980B9); 224 | background-image: -o-linear-gradient(top, #3498DB, #2980B9); 225 | background-image: linear-gradient(to bottom, #3498DB, #2980B9); 226 | -webkit-border-radius: 12; 227 | -moz-border-radius: 12; 228 | border-radius: 6px; 229 | font-family: Arial; 230 | color: #FFFFFF; 231 | font-size: 15px; 232 | padding: 3px; 233 | border: solid #1F628D 0px; 234 | text-decoration: none; 235 | } 236 | 237 | button:active, input[type="submit"]:active { 238 | filter: brightness(120%); 239 | } 240 | 241 | .btn:hover { 242 | background: #3CB0FD; 243 | background-image: -webkit-linear-gradient(top, #3CB0FD, #3498DB); 244 | background-image: -moz-linear-gradient(top, #3CB0FD, #3498DB); 245 | background-image: -ms-linear-gradient(top, #3CB0FD, #3498DB); 246 | background-image: -o-linear-gradient(top, #3CB0FD, #3498DB); 247 | background-image: linear-gradient(to bottom, #3CB0FD, #3498DB); 248 | text-decoration: none; 249 | } 250 | 251 | .fileUpload { 252 | position: relative; 253 | overflow: hidden; 254 | margin: 10px; 255 | } 256 | 257 | .fileUpload input.upload { 258 | position: absolute; 259 | top: 0; 260 | right: 0; 261 | margin: 0; 262 | padding: 0; 263 | font-size: 20px; 264 | cursor: pointer; 265 | opacity: 0; 266 | filter: alpha(opacity=0); 267 | } 268 | 269 | /* The Modal (background) */ 270 | .modal { 271 | display: none; 272 | /* Hidden by default */ 273 | position: fixed; 274 | /* Stay in place */ 275 | z-index: 1; 276 | /* Sit on top */ 277 | left: 0; 278 | top: 0; 279 | width: 100%; 280 | /* Full width */ 281 | height: 100%; 282 | /* Full height */ 283 | overflow: auto; 284 | /* Enable scroll if needed */ 285 | background-color: rgb(0, 0, 0); 286 | /* Fallback color */ 287 | background-color: rgba(0, 0, 0, 0.4); 288 | /* Black w/ opacity */ 289 | } 290 | 291 | /* Modal Content/Box */ 292 | .modal-content { 293 | background-color: #FEFEFE; 294 | margin: 15% auto; 295 | /* 15% from the top and centered */ 296 | padding: 20px; 297 | border: 1px solid #888; 298 | width: 300px; 299 | /* Could be more or less, depending on screen size */ 300 | } 301 | 302 | /* The Close Button */ 303 | .close { 304 | color: #AAA; 305 | float: right; 306 | font-size: 28px; 307 | font-weight: bold; 308 | } 309 | 310 | .close:hover, .close:focus { 311 | color: black; 312 | text-decoration: none; 313 | cursor: pointer; 314 | } 315 | -------------------------------------------------------------------------------- /web/switch.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Thibaut Courouble 3 | * http://www.cssflow.com 4 | * 5 | * Licensed under the MIT License: 6 | * http://www.opensource.org/licenses/mit-license.php 7 | */ 8 | 9 | .container 10 | { 11 | margin: 50px auto; 12 | width: 280px; 13 | text-align: center; 14 | } 15 | .container > .switch 16 | { 17 | display: block; 18 | margin: 12px auto; 19 | } 20 | .switch 21 | { 22 | position: relative; 23 | display: inline-block; 24 | vertical-align: top; 25 | width: 56px; 26 | height: 20px; 27 | padding: 3px; 28 | background-color: white; 29 | border-radius: 18px; 30 | box-shadow: inset 0 -1px white, inset 0 1px 1px rgba(0, 0, 0, 0.05); 31 | cursor: pointer; 32 | background-image: -webkit-linear-gradient(top, #EEEEEE, white 25px); 33 | background-image: -moz-linear-gradient(top, #EEEEEE, white 25px); 34 | background-image: -o-linear-gradient(top, #EEEEEE, white 25px); 35 | background-image: linear-gradient(to bottom, #EEEEEE, white 25px); 36 | } 37 | .switch-input 38 | { 39 | position: absolute; 40 | top: 0; 41 | left: 0; 42 | opacity: 0; 43 | } 44 | .switch-label 45 | { 46 | position: relative; 47 | display: block; 48 | height: inherit; 49 | font-size: 10px; 50 | text-transform: uppercase; 51 | background: #ECEEEF; 52 | border-radius: inherit; 53 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.15); 54 | -webkit-transition: 0.15s ease-out; 55 | -moz-transition: 0.15s ease-out; 56 | -o-transition: 0.15s ease-out; 57 | transition: 0.15s ease-out; 58 | -webkit-transition-property: opacity background; 59 | -moz-transition-property: opacity background; 60 | -o-transition-property: opacity background; 61 | transition-property: opacity background; 62 | } 63 | .switch-label:before, .switch-label:after 64 | { 65 | position: absolute; 66 | top: 50%; 67 | margin-top: -0.5em; 68 | line-height: 1; 69 | -webkit-transition: inherit; 70 | -moz-transition: inherit; 71 | -o-transition: inherit; 72 | transition: inherit; 73 | } 74 | .switch-label:before 75 | { 76 | content: attr(data-off); 77 | right: 11px; 78 | color: #AAA; 79 | text-shadow: 0 1px rgba(255, 255, 255, 0.5); 80 | } 81 | .switch-label:after 82 | { 83 | content: attr(data-on); 84 | left: 11px; 85 | color: white; 86 | text-shadow: 0 1px rgba(0, 0, 0, 0.2); 87 | opacity: 0; 88 | } 89 | .switch-input:checked ~ .switch-label 90 | { 91 | background: #47A8D8; 92 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15), inset 0 0 3px rgba(0, 0, 0, 0.2); 93 | } 94 | .switch-input:checked ~ .switch-label:before 95 | { 96 | opacity: 0; 97 | } 98 | .switch-input:checked ~ .switch-label:after 99 | { 100 | opacity: 1; 101 | } 102 | .switch-handle 103 | { 104 | position: absolute; 105 | top: 4px; 106 | left: 4px; 107 | width: 18px; 108 | height: 18px; 109 | background: white; 110 | border-radius: 10px; 111 | box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2); 112 | background-image: -webkit-linear-gradient(top, white 40%, #F0F0F0); 113 | background-image: -moz-linear-gradient(top, white 40%, #F0F0F0); 114 | background-image: -o-linear-gradient(top, white 40%, #F0F0F0); 115 | background-image: linear-gradient(to bottom, white 40%, #F0F0F0); 116 | -webkit-transition: left 0.15s ease-out; 117 | -moz-transition: left 0.15s ease-out; 118 | -o-transition: left 0.15s ease-out; 119 | transition: left 0.15s ease-out; 120 | } 121 | .switch-handle:before 122 | { 123 | content: ''; 124 | position: absolute; 125 | top: 50%; 126 | left: 50%; 127 | margin: -6px 0 0 -6px; 128 | width: 12px; 129 | height: 12px; 130 | background: #F9F9F9; 131 | border-radius: 6px; 132 | box-shadow: inset 0 1px rgba(0, 0, 0, 0.02); 133 | background-image: -webkit-linear-gradient(top, #EEEEEE, white); 134 | background-image: -moz-linear-gradient(top, #EEEEEE, white); 135 | background-image: -o-linear-gradient(top, #EEEEEE, white); 136 | background-image: linear-gradient(to bottom, #EEEEEE, white); 137 | } 138 | .switch-input:checked ~ .switch-handle 139 | { 140 | left: 40px; 141 | box-shadow: -1px 1px 5px rgba(0, 0, 0, 0.2); 142 | } 143 | .switch-green > .switch-input:checked ~ .switch-label 144 | { 145 | background: #4FB845; 146 | } 147 | -------------------------------------------------------------------------------- /web/zepto_extras.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | // Used by dateinput 3 | $.expr = {':': {}}; 4 | 5 | // Used by bootstrap 6 | $.support = {}; 7 | 8 | // Used by dateinput 9 | $.fn.clone = function(){ 10 | var ret = $(); 11 | this.each(function(){ 12 | ret.push(this.cloneNode(true)) 13 | }); 14 | return ret; 15 | }; 16 | 17 | ["Left", "Top"].forEach(function(name, i) { 18 | var method = "scroll" + name; 19 | 20 | function isWindow( obj ) { 21 | return obj && typeof obj === "object" && "setInterval" in obj; 22 | } 23 | 24 | function getWindow( elem ) { 25 | return isWindow( elem ) ? elem : elem.nodeType === 9 ? elem.defaultView || elem.parentWindow : false; 26 | } 27 | 28 | $.fn[ method ] = function( val ) { 29 | var elem, win; 30 | 31 | if ( val === undefined ) { 32 | 33 | elem = this[ 0 ]; 34 | 35 | if ( !elem ) { 36 | return null; 37 | } 38 | 39 | win = getWindow( elem ); 40 | 41 | // Return the scroll offset 42 | return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : 43 | win.document.documentElement[ method ] || 44 | win.document.body[ method ] : 45 | elem[ method ]; 46 | } 47 | 48 | // Set the scroll offset 49 | this.each(function() { 50 | win = getWindow( this ); 51 | 52 | if ( win ) { 53 | var xCoord = !i ? val : $( win ).scrollLeft(); 54 | var yCoord = i ? val : $( win ).scrollTop(); 55 | win.scrollTo(xCoord, yCoord); 56 | } else { 57 | this[ method ] = val; 58 | } 59 | }); 60 | } 61 | }); 62 | 63 | // Used by colorslider.js 64 | ['width', 'height'].forEach(function(dimension) { 65 | var offset, Dimension = dimension.replace(/./, function(m) { return m[0].toUpperCase() }); 66 | $.fn['outer' + Dimension] = function(margin) { 67 | var elem = this; 68 | if (elem) { 69 | var size = elem[dimension](); 70 | var sides = {'width': ['left', 'right'], 'height': ['top', 'bottom']}; 71 | sides[dimension].forEach(function(side) { 72 | if (margin) size += parseInt(elem.css('margin-' + side), 10); 73 | }); 74 | return size; 75 | } else { 76 | return null; 77 | } 78 | }; 79 | }); 80 | 81 | // Used by bootstrap 82 | $.proxy = function( fn, context ) { 83 | if ( typeof context === "string" ) { 84 | var tmp = fn[ context ]; 85 | context = fn; 86 | fn = tmp; 87 | } 88 | 89 | // Quick check to determine if target is callable, in the spec 90 | // this throws a TypeError, but we will just return undefined. 91 | if ( !$.isFunction( fn ) ) { 92 | return undefined; 93 | } 94 | 95 | // Simulated bind 96 | var args = Array.prototype.slice.call( arguments, 2 ), 97 | proxy = function() { 98 | return fn.apply( context, args.concat( Array.prototype.slice.call( arguments ) ) ); 99 | }; 100 | 101 | // Set the guid of unique handler to the same of original handler, so it can be removed 102 | proxy.guid = fn.guid = fn.guid || proxy.guid || $.guid++; 103 | 104 | return proxy; 105 | }; 106 | 107 | // Used by timeago 108 | var nativeTrim = String.prototype.trim; 109 | $.trim = function(str, characters){ 110 | if (!characters && nativeTrim) { 111 | return nativeTrim.call(str); 112 | } 113 | characters = defaultToWhiteSpace(characters); 114 | return str.replace(new RegExp('\^[' + characters + ']+|[' + characters + ']+$', 'g'), ''); 115 | }; 116 | 117 | // Used by util.js 118 | var rtable = /^t(?:able|d|h)$/i, 119 | rroot = /^(?:body|html)$/i; 120 | $.fn.position = function() { 121 | if ( !this[0] ) { 122 | return null; 123 | } 124 | 125 | var elem = this[0], 126 | 127 | // Get *real* offsetParent 128 | offsetParent = this.offsetParent(), 129 | // Get correct offsets 130 | offset = this.offset(), 131 | parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); 132 | 133 | // Subtract element margins 134 | // note: when an element has margin: auto the offsetLeft and marginLeft 135 | // are the same in Safari causing offset.left to incorrectly be 0 136 | offset.top -= parseFloat( $(elem).css("margin-top") ) || 0; 137 | offset.left -= parseFloat( $(elem).css("margin-left") ) || 0; 138 | 139 | // Add offsetParent borders 140 | parentOffset.top += parseFloat( $(offsetParent[0]).css("border-top-width") ) || 0; 141 | parentOffset.left += parseFloat( $(offsetParent[0]).css("border-left-width") ) || 0; 142 | 143 | // Subtract the two offsets 144 | return { 145 | top: offset.top - parentOffset.top, 146 | left: offset.left - parentOffset.left 147 | }; 148 | }; 149 | 150 | $.fn.offsetParent = function() { 151 | var ret = $(); 152 | this.each(function(){ 153 | var offsetParent = this.offsetParent || document.body; 154 | while ( offsetParent && (!rroot.test(offsetParent.nodeName) && $(offsetParent).css("position") === "static") ) { 155 | offsetParent = offsetParent.offsetParent; 156 | } 157 | ret.push(offsetParent); 158 | }); 159 | return ret; 160 | }; 161 | 162 | // For dateinput 163 | Event.prototype.isDefaultPrevented = function() { 164 | return this.defaultPrevented; 165 | }; 166 | })(Zepto); -------------------------------------------------------------------------------- /xmlstring.cpp: -------------------------------------------------------------------------------- 1 | #include "xmlstring.h" 2 | 3 | 4 | 5 | xmlstring::xmlstring() 6 | { 7 | } 8 | 9 | 10 | xmlstring::~xmlstring() 11 | { 12 | } 13 | 14 | void xmlstring::catTag(const __FlashStringHelper *tag, String value) 15 | { 16 | *this += "<"; 17 | *this += tag; 18 | *this += ">" + value + ""; 19 | } 20 | 21 | void xmlstring::catTag(const __FlashStringHelper *tag, int value) 22 | { 23 | catTag(tag, String(value)); 24 | } -------------------------------------------------------------------------------- /xmlstring.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "WString.h" 3 | class xmlstring : 4 | public String 5 | { 6 | public: 7 | xmlstring(); 8 | ~xmlstring(); 9 | void catTag(const __FlashStringHelper * tag, String value); 10 | void catTag(const __FlashStringHelper * tag, int value); 11 | }; 12 | 13 | --------------------------------------------------------------------------------