├── .gitignore ├── library.properties ├── README.md ├── examples ├── ModbusRtuSlaveSimple │ └── ModbusRtuSlaveSimple.ino ├── UDPSimple │ └── UDPSimple.ino ├── RS485Echo │ └── RS485Echo.ino ├── WebSubscribe │ └── WebSubscribe.ino ├── WebAPI │ └── WebAPI.ino ├── OneWireTempSens │ └── OneWireTempSens.ino ├── IonoMkrMQTT │ ├── Watchdog.h │ └── IonoMkrMQTT.ino ├── IonoMkrLoRaWAN │ ├── Watchdog.h │ ├── CayenneLPP.h │ ├── CayenneLPP.cpp │ ├── IonoMkrLoRaWAN.ino │ └── SerialConfig.h ├── IonoIO │ └── IonoIO.ino ├── WebApp │ ├── iono-min.html │ ├── iono.html │ └── WebApp.ino ├── ModbusTcpServerYun │ └── ModbusTcpServerYun.ino └── ModbusRtuApp │ └── ModbusRtuApp.ino ├── keywords.txt └── src ├── IonoUDP.h ├── IonoWeb.h ├── IonoModbusRtuSlave.h ├── Iono.h ├── IonoUDP.cpp ├── Iono.cpp ├── WebServer.h ├── IonoWeb.cpp └── IonoModbusRtuSlave.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .DS_Store? 3 | .Trashes 4 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Iono Uno-MKR-RP 2 | version=3.2.2 3 | author=Sfera Labs 4 | maintainer=Sfera Labs 5 | sentence=Libraries and examples for Iono Uno/MKR/RP 6 | paragraph=Iono Uno, Iono MKR, and Iono RP bring your Arduino programming skills to professional, industrial applications 7 | category=Device Control 8 | url=https://github.com/sfera-labs/iono 9 | dot_a_linkage=true 10 | depends=Ethernet,Ethernet2,WiFiNINA,FlashAsEEPROM,EEPROM,OneWire,DallasTemperature,ArduinoMqttClient,Wiegand,Modbus RTU Slave 11 | includes=Iono.h 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Iono Uno / Iono MKR / Iono RP - Arduino IDE libraries and examples 2 | 3 | This repository contains Arduino libraries to be used with [Iono Uno](https://www.sferalabs.cc/product/iono-uno/), [Iono MKR](https://www.sferalabs.cc/product/iono-mkr/) and [Iono RP](https://www.sferalabs.cc/product/iono-rp/). 4 | 5 | Iono is a general-purpose, professional input/output module (a.k.a. PLC) based on a standard [Arduino](http://www.arduino.cc/) or [Raspberry Pi RP2040](https://www.raspberrypi.org/products/rp2040/) microcontroller. 6 | 7 | Iono allows you to use your Arduino programming skills, and the vast amount of software available for it, not only for prototypes, but for professional, industrial applications where extreme reliability, ruggedness and compliance with directives for EMC, Electrical Safety and RoHS are required. 8 | 9 | Refer to the [Wiki](https://github.com/sfera-labs/iono/wiki) for usage details, examples and ready-to-use applications. 10 | 11 | For more info about Iono visit www.sferalabs.cc 12 | -------------------------------------------------------------------------------- /examples/ModbusRtuSlaveSimple/ModbusRtuSlaveSimple.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusRtuSlaveSimple.ino - Arduino sketch showing the use of the IonoModbusRtuSlave library 3 | 4 | Copyright (C) 2018-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include 17 | 18 | void setup() { 19 | /** 20 | * Start the modbus server with: 21 | * unit address: 10 22 | * baud rate: 115200 23 | * data bits: 8 24 | * parity: even 25 | * stop bits: 1 26 | * debounce time on digital inputs: 50ms 27 | */ 28 | IonoModbusRtuSlave.begin(10, 115200, SERIAL_8E1, 50); 29 | } 30 | 31 | void loop() { 32 | /** 33 | * Process requests 34 | */ 35 | IonoModbusRtuSlave.process(); 36 | } 37 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | Iono KEYWORD1 2 | IonoUDP KEYWORD1 3 | IonoWeb KEYWORD1 4 | WebServer KEYWORD1 5 | IonoEQ KEYWORD1 6 | read KEYWORD2 7 | write KEYWORD2 8 | flip KEYWORD2 9 | subscribeDigital KEYWORD2 10 | subscribeAnalog KEYWORD2 11 | process KEYWORD2 12 | begin KEYWORD2 13 | processRequest KEYWORD2 14 | subscribe KEYWORD2 15 | attachShutoffISR KEYWORD2 16 | detachShutoffISR KEYWORD2 17 | attachProcessingISR KEYWORD2 18 | detachProcessingISR KEYWORD2 19 | readD7S KEYWORD2 20 | writeD7S KEYWORD2 21 | adjust KEYWORD2 22 | readState KEYWORD2 23 | readAxisState KEYWORD2 24 | readEvent KEYWORD2 25 | readMode KEYWORD2 26 | writeMode KEYWORD2 27 | readCtrl KEYWORD2 28 | writeCtrl KEYWORD2 29 | readClearCommand KEYWORD2 30 | writeClearCommand KEYWORD2 31 | readCurrentSI KEYWORD2 32 | readCurrentPGA KEYWORD2 33 | readLatestOffsetX KEYWORD2 34 | readLatestOffsetY KEYWORD2 35 | readLatestOffsetZ KEYWORD2 36 | readLatestTemp KEYWORD2 37 | readLatestSI KEYWORD2 38 | readLatestPGA KEYWORD2 39 | readRankedOffsetX KEYWORD2 40 | readRankedOffsetY KEYWORD2 41 | readRankedOffsetZ KEYWORD2 42 | readRankedTemp KEYWORD2 43 | readRankedSI KEYWORD2 44 | readRankedPGA KEYWORD2 45 | -------------------------------------------------------------------------------- /examples/UDPSimple/UDPSimple.ino: -------------------------------------------------------------------------------- 1 | /* 2 | UDPSimple.ino - Arduino sketch showing the use of the IonoUDP library 3 | 4 | Copyright (C) 2014-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | byte mac[] = { 0x90, 0xA2, 0xDA, 0x0E, 0xD5, 0x8C }; 22 | 23 | IPAddress ip(192, 168, 1, 243); 24 | IPAddress gateway(192, 168, 1, 1); 25 | IPAddress subnet(255, 255, 255, 0); 26 | 27 | unsigned int port = 7878; 28 | 29 | EthernetUDP Udp; 30 | 31 | void setup() { 32 | Ethernet.begin(mac, ip, gateway, subnet); 33 | Udp.begin(port); 34 | IonoUDP.begin("myIono", Udp, port, 50, 0.1); 35 | } 36 | 37 | void loop() { 38 | IonoUDP.process(); 39 | delay(10); 40 | } 41 | -------------------------------------------------------------------------------- /src/IonoUDP.h: -------------------------------------------------------------------------------- 1 | /* 2 | IonoUDP.h - Arduino library for the control of Iono Uno Ethernet via a simple UDP protocol 3 | 4 | Copyright (C) 2014-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #ifndef IonoUDP_h 17 | #define IonoUDP_h 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #define COMMAND_MAX_SIZE 10 24 | 25 | class IonoUDPClass 26 | { 27 | public: 28 | IonoUDPClass(); 29 | void begin(const char *id, EthernetUDP Udp, unsigned int port, unsigned long stableTime, float minVariation); 30 | void process(); 31 | 32 | private: 33 | static char _pinName[][4]; 34 | 35 | IPAddress _ipBroadcast; 36 | float _lastValue[20]; 37 | float _value[20]; 38 | unsigned long _lastTS[20]; 39 | char _progr; 40 | const char *_id; 41 | unsigned int _port; 42 | EthernetUDP _Udp; 43 | char _command[COMMAND_MAX_SIZE]; 44 | unsigned long _stableTime; 45 | float _minVariation; 46 | unsigned long _lastSend; 47 | 48 | void checkState(); 49 | void check(int pin); 50 | void send(int pin, float val); 51 | void ftoa(char *sVal, float fVal); 52 | void checkCommands(); 53 | }; 54 | 55 | extern IonoUDPClass IonoUDP; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /examples/RS485Echo/RS485Echo.ino: -------------------------------------------------------------------------------- 1 | /* 2 | RS485Echo.ino - Arduino sketch showing the use of the RS-485 interface. 3 | 4 | Copyright (C) 2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | 15 | This sketch reads whatever is sent on the RS-485 port and echoes it back. 16 | */ 17 | 18 | #include 19 | 20 | #define MAX_LEN 512 21 | 22 | byte rxBuff[MAX_LEN + 1]; 23 | int rxIdx; 24 | 25 | void setup() { 26 | Iono.setup(); 27 | 28 | /** 29 | * Init port 30 | * baud rate: 19200 31 | * data bits: 8 32 | * parity: none 33 | * stop bits: 1 34 | */ 35 | IONO_RS485.begin(19200, SERIAL_8N1); 36 | } 37 | 38 | void loop() { 39 | if (IONO_RS485.available() > 0) { 40 | rxIdx = 0; 41 | 42 | // Read into buffer while data is available 43 | while(IONO_RS485.available() > 0 && rxIdx <= MAX_LEN) { 44 | rxBuff[rxIdx++] = IONO_RS485.read(); 45 | if (IONO_RS485.available() == 0) { 46 | // give it some extra time to check if 47 | // any other data is on its way... 48 | delay(50); 49 | } 50 | } 51 | 52 | Iono.serialTxEn(true); 53 | 54 | IONO_RS485.write(rxBuff, rxIdx); 55 | IONO_RS485.flush(); 56 | 57 | Iono.serialTxEn(false); 58 | } 59 | 60 | delay(50); 61 | } 62 | -------------------------------------------------------------------------------- /examples/WebSubscribe/WebSubscribe.ino: -------------------------------------------------------------------------------- 1 | /* 2 | WebSubscribe.ino - IonoWeb library example for Iono Uno Ethernet 3 | 4 | Copyright (C) 2014-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | byte mac[] = { 0x90, 0xA2, 0xDA, 0x0E, 0xD5, 0x8C }; 22 | 23 | IPAddress ip(192, 168, 1, 243); 24 | IPAddress gateway(192, 168, 1, 1); 25 | IPAddress subnet(255, 255, 255, 0); 26 | 27 | void setup() { 28 | Iono.setup(); 29 | Ethernet.begin(mac, ip, gateway, subnet); 30 | IonoWeb.begin(80); 31 | 32 | /* 33 | / Every time a pin changes value 34 | / and is stable for 100ms 35 | / execute ah HTTP GET request to 36 | / "192.168.1.242:8080/bar?=" 37 | / where is substituted by the 38 | / name of the pin (i.e. "DI1" or "AV3") and 39 | / by the current value of 40 | / the pin. 41 | / For inputs read as voltage or current 42 | / the call will be triggered only if the 43 | / value changes of more than 0.1 V or mA 44 | / Input 1 will be read as digital (1), 45 | / Input 2 will be read as digital (1), 46 | / Input 3 will be read as voltage (2), 47 | / Input 4 will be read as current (3). 48 | / Request examples: 49 | / http://192.168.1.242:8080/bar?DI1=1 50 | / http://192.168.1.242:8080/bar?AV3=5.30 51 | */ 52 | IonoWeb.subscribe(100, 0.1, "192.168.1.242", 8080, "/bar", 1, 1, 2, 3); 53 | } 54 | 55 | void loop() { 56 | // Check all the inputs 57 | Iono.process(); 58 | } 59 | -------------------------------------------------------------------------------- /src/IonoWeb.h: -------------------------------------------------------------------------------- 1 | /* 2 | IonoWeb.h - Arduino library for the control of Iono Uno Ethernet via HTTP API 3 | 4 | Copyright (C) 2014-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #ifndef IonoWeb_h 17 | #define IonoWeb_h 18 | 19 | #include 20 | #include "WebServer.h" 21 | 22 | #define SUBSCRIBE_TIMEOUT 60000 23 | 24 | class IonoWebClass 25 | { 26 | public: 27 | static void begin(int port); 28 | static void processRequest(); 29 | static void subscribe(unsigned long stableTime, float minVariation, char *host, int port, char *command, uint8_t mode1, uint8_t mode2, uint8_t mode3, uint8_t mode4); 30 | static WebServer& getWebServer(); 31 | 32 | private: 33 | static WebServer _webServer; 34 | 35 | static char *_host; 36 | static int _port; 37 | static char *_command; 38 | static unsigned long _lastSubscribeTime; 39 | 40 | static void setCommand(WebServer &webServer, WebServer::ConnectionType type, char* urlTail, bool tailComplete); 41 | static void jsonStateCommand(WebServer &webServer, WebServer::ConnectionType type, char* urlTail, bool tailComplete); 42 | static void subscribeCommand(WebServer &webServer, WebServer::ConnectionType type, char* urlTail, bool tailComplete); 43 | static void callDigitalURL(uint8_t pin, float value); 44 | static void callAnalogURL(uint8_t pin, float value); 45 | static void callURL(const char *pin, const char *value); 46 | static void ftoa(char *sVal, float fVal); 47 | }; 48 | 49 | extern IonoWebClass IonoWeb; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /examples/WebAPI/WebAPI.ino: -------------------------------------------------------------------------------- 1 | /* 2 | WebAPI.ino - IonoWeb library example for Iono Uno Ethernet 3 | 4 | Copyright (C) 2014-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | /* 17 | / This example allows for the control of 18 | / Iono Uno Ethernet using simple HTTP requests. 19 | / Here some examples: 20 | / 21 | / http://192.168.1.243/api/state 22 | / returns a json object representing the 23 | / current state of Iono's inputs and outputs 24 | / 25 | / http://192.168.1.243/api/set?DO1=1&DO2=1&AO1=5.30 26 | / switches on relay DO1 and DO2 and sets a 27 | / 5.30V voltage on analog autput AO1 28 | / 29 | / http://192.168.1.243/api/subscribe?mv=0.1&st=100&host=192.168.1.242&port=8080&cmd=/bar&mode1=d&mode2=d&mode3=v&mode4=i 30 | / Every time a pin changes value 31 | / and is stable for 100ms (st=100) 32 | / execute ah HTTP GET request to 33 | / "192.168.1.242:8080/bar?=" 34 | / where is substituted by the 35 | / name of the pin (i.e. "DI1" or "AV3") and 36 | / by the current value of 37 | / the pin. 38 | / For inputs read as voltage or current 39 | / the call will be triggered only if the 40 | / value changes of more than 0.1 V or mA (mv=0.1) 41 | / Input 1 will be read as digital (mode1=d), 42 | / Input 2 will be read as digital (mode2=d), 43 | / Input 3 will be read as voltage (mode3=v), 44 | / Input 4 will be read as current (mode4=i). 45 | */ 46 | 47 | #include 48 | #include 49 | #include 50 | #include 51 | 52 | byte mac[] = { 0x90, 0xA2, 0xDA, 0x0E, 0xD5, 0x8C }; 53 | 54 | IPAddress ip(192, 168, 1, 243); 55 | IPAddress gateway(192, 168, 1, 1); 56 | IPAddress subnet(255, 255, 255, 0); 57 | 58 | void setup() { 59 | Iono.setup(); 60 | Ethernet.begin(mac, ip, gateway, subnet); 61 | IonoWeb.begin(80); 62 | } 63 | 64 | void loop() { 65 | // Process incoming requests 66 | IonoWeb.processRequest(); 67 | // Check all the inputs - needed only for subscribe 68 | Iono.process(); 69 | } 70 | -------------------------------------------------------------------------------- /src/IonoModbusRtuSlave.h: -------------------------------------------------------------------------------- 1 | /* 2 | IonoModbusRtuSlave.h - Modbus RTU Slave library for Iono Uno/MKR/RP 3 | 4 | Copyright (C) 2018-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #ifndef IonoModbusRtuSlave_h 17 | #define IonoModbusRtuSlave_h 18 | 19 | #include 20 | #include 21 | 22 | #define MB_RESP_PASS 0xFE 23 | 24 | class IonoModbusRtuSlaveClass { 25 | public: 26 | static void begin(byte unitAddr, unsigned long baud, unsigned long config, unsigned long diDebounceTime); 27 | static void process(); 28 | static void setCustomHandler(ModbusRtuSlaveClass::Callback *callback); 29 | static void setInputMode(int idx, char mode); 30 | static void subscribeDigital(uint8_t pin, IonoClass::Callback *callback); 31 | 32 | private: 33 | static bool _di1deb; 34 | static bool _di2deb; 35 | static bool _di3deb; 36 | static bool _di4deb; 37 | static bool _di5deb; 38 | static bool _di6deb; 39 | 40 | static word _di1count; 41 | static word _di2count; 42 | static word _di3count; 43 | static word _di4count; 44 | static word _di5count; 45 | static word _di6count; 46 | 47 | static IonoClass::Callback *_di1Callback; 48 | static IonoClass::Callback *_di2Callback; 49 | static IonoClass::Callback *_di3Callback; 50 | static IonoClass::Callback *_di4Callback; 51 | static IonoClass::Callback *_di5Callback; 52 | static IonoClass::Callback *_di6Callback; 53 | 54 | static char _inMode[4]; 55 | 56 | static ModbusRtuSlaveClass::Callback *_customCallback; 57 | 58 | static byte onRequest(byte unitAddr, byte function, word regAddr, word qty, byte *data); 59 | static void onDIChange(uint8_t pin, float value); 60 | static bool checkAddrRange(word regAddr, word qty, word min, word max); 61 | static uint8_t indexToDO(int i); 62 | static uint8_t indexToDI(int i); 63 | static bool indexToDIdeb(int i); 64 | static word indexToDIcount(int i); 65 | static uint8_t indexToAV(int i); 66 | static uint8_t indexToAI(int i); 67 | }; 68 | 69 | extern IonoModbusRtuSlaveClass IonoModbusRtuSlave; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /examples/OneWireTempSens/OneWireTempSens.ino: -------------------------------------------------------------------------------- 1 | /* 2 | OneWireTempSens.ino - 1-Wire Dallas temperature sensors example 3 | 4 | Copyright (C) 2022-2025 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | 15 | N.B.: Use 5V-compatible 1-Wire sensors 16 | 17 | This sketch requires the "OneWire" and "DallasTemperature" Arduino libraries 18 | 19 | Usage: 20 | - Place the DI6 internal jumper into BYP position 21 | - Connect the 5V (red/brown) wire of the sensor(s) to 5VO on Iono MKR, or to AO1 on Iono Uno 22 | - Connect the ground (black) wire to GND and the data (yellow/blue/...) wire to DI6 23 | - On Iono Uno you may need a pull-up resistor between DI6 and AO1 (10K should work) 24 | - Run the sketch and check the output in the Serial Monitor 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #define MAX_SENSORS 20 32 | 33 | OneWire oneWire(IONO_PIN_DI6_BYP); 34 | DallasTemperature sensors(&oneWire); 35 | 36 | DeviceAddress addrs[MAX_SENSORS]; 37 | 38 | int n = 0; 39 | 40 | void setup(void) { 41 | Iono.setup(); 42 | 43 | #ifdef IONO_UNO 44 | Iono.write(AO1, 5); 45 | #endif 46 | 47 | Serial.begin(9600); 48 | while(!Serial); 49 | 50 | Serial.println("=== Iono 1-Wire Dallas temperature sensors demo ==="); 51 | 52 | do { 53 | sensors.begin(); 54 | 55 | n = sensors.getDeviceCount(); 56 | if (n > MAX_SENSORS) { 57 | n = MAX_SENSORS; 58 | } 59 | 60 | Serial.print("Found "); 61 | Serial.print(n); 62 | Serial.println(" sensor(s)."); 63 | 64 | delay(500); 65 | } while (n <= 0); 66 | 67 | for (int i = 0; i < n; i++) { 68 | sensors.getAddress(addrs[i], i); 69 | } 70 | } 71 | 72 | void loop(void) { 73 | Serial.print("Requesting temperatures..."); 74 | sensors.requestTemperatures(); 75 | Serial.println(" done!"); 76 | 77 | for (int i = 0; i < n; i++) { 78 | printAddress(addrs[i]); 79 | Serial.print(": "); 80 | Serial.println(sensors.getTempC(addrs[i])); 81 | } 82 | 83 | delay(1000); 84 | } 85 | 86 | void printAddress(DeviceAddress deviceAddress) { 87 | for (uint8_t i = 0; i < 8; i++) { 88 | if (deviceAddress[i] < 16) Serial.print("0"); 89 | Serial.print(deviceAddress[i], HEX); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /examples/IonoMkrMQTT/Watchdog.h: -------------------------------------------------------------------------------- 1 | /* 2 | Watchdog.h 3 | 4 | Copyright (C) 2020-2023 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #ifndef Watchdog_h 17 | #define Watchdog_h 18 | 19 | class Watchdog { 20 | private: 21 | static unsigned long _ts; 22 | 23 | public: 24 | static void setup(); 25 | static void disable(); 26 | static void clear(); 27 | }; 28 | 29 | unsigned long Watchdog::_ts; 30 | 31 | void Watchdog::disable() { 32 | REG_WDT_CTRL &= ~WDT_CTRL_ENABLE; 33 | while(WDT->STATUS.bit.SYNCBUSY); 34 | } 35 | 36 | void Watchdog::setup() { 37 | // Set up the generic clock (GCLK2) used to clock the watchdog timer at 1.024kHz 38 | REG_GCLK_GENDIV = GCLK_GENDIV_DIV(4) | // Divide the 32.768kHz clock source by divisor 32, where 2^(4 + 1): 32.768kHz/32=1.024kHz 39 | GCLK_GENDIV_ID(2); // Select Generic Clock (GCLK) 2 40 | while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization 41 | 42 | REG_GCLK_GENCTRL = GCLK_GENCTRL_DIVSEL | // Set to divide by 2^(GCLK_GENDIV_DIV(4) + 1) 43 | GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW 44 | GCLK_GENCTRL_GENEN | // Enable GCLK2 45 | GCLK_GENCTRL_SRC_OSCULP32K | // Set the clock source to the ultra low power oscillator (OSCULP32K) 46 | GCLK_GENCTRL_ID(2); // Select GCLK2 47 | while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization 48 | 49 | // Feed GCLK2 to WDT (Watchdog Timer) 50 | REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK2 to the WDT 51 | GCLK_CLKCTRL_GEN_GCLK2 | // Select GCLK2 52 | GCLK_CLKCTRL_ID_WDT; // Feed the GCLK2 to the WDT 53 | while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization 54 | 55 | REG_WDT_CONFIG = WDT_CONFIG_PER_16K; // Set the WDT reset timeout to about 16 second 56 | while(WDT->STATUS.bit.SYNCBUSY); // Wait for synchronization 57 | REG_WDT_CTRL = WDT_CTRL_ENABLE; // Enable the WDT in normal mode 58 | while(WDT->STATUS.bit.SYNCBUSY); // Wait for synchronization 59 | 60 | _ts = millis(); 61 | } 62 | 63 | void Watchdog::clear() { 64 | if (!WDT->STATUS.bit.SYNCBUSY && millis() - _ts >= 300) { 65 | REG_WDT_CLEAR = WDT_CLEAR_CLEAR_KEY; 66 | _ts = millis(); 67 | } 68 | } 69 | 70 | extern Watchdog Watchdog; 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /examples/IonoMkrLoRaWAN/Watchdog.h: -------------------------------------------------------------------------------- 1 | /* 2 | Watchdog.h 3 | 4 | Copyright (C) 2018-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #ifndef Watchdog_h 17 | #define Watchdog_h 18 | 19 | class Watchdog { 20 | private: 21 | static unsigned long _ts; 22 | 23 | public: 24 | static void setup(); 25 | static void disable(); 26 | static void clear(); 27 | }; 28 | 29 | unsigned long Watchdog::_ts; 30 | 31 | void Watchdog::disable() { 32 | REG_WDT_CTRL &= ~WDT_CTRL_ENABLE; 33 | while(WDT->STATUS.bit.SYNCBUSY); 34 | } 35 | 36 | void Watchdog::setup() { 37 | // Set up the generic clock (GCLK2) used to clock the watchdog timer at 1.024kHz 38 | REG_GCLK_GENDIV = GCLK_GENDIV_DIV(4) | // Divide the 32.768kHz clock source by divisor 32, where 2^(4 + 1): 32.768kHz/32=1.024kHz 39 | GCLK_GENDIV_ID(2); // Select Generic Clock (GCLK) 2 40 | while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization 41 | 42 | REG_GCLK_GENCTRL = GCLK_GENCTRL_DIVSEL | // Set to divide by 2^(GCLK_GENDIV_DIV(4) + 1) 43 | GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW 44 | GCLK_GENCTRL_GENEN | // Enable GCLK2 45 | GCLK_GENCTRL_SRC_OSCULP32K | // Set the clock source to the ultra low power oscillator (OSCULP32K) 46 | GCLK_GENCTRL_ID(2); // Select GCLK2 47 | while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization 48 | 49 | // Feed GCLK2 to WDT (Watchdog Timer) 50 | REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK2 to the WDT 51 | GCLK_CLKCTRL_GEN_GCLK2 | // Select GCLK2 52 | GCLK_CLKCTRL_ID_WDT; // Feed the GCLK2 to the WDT 53 | while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization 54 | 55 | REG_WDT_CONFIG = WDT_CONFIG_PER_8K; // Set the WDT reset timeout to about 8 second 56 | while(WDT->STATUS.bit.SYNCBUSY); // Wait for synchronization 57 | REG_WDT_CTRL = WDT_CTRL_ENABLE; // Enable the WDT in normal mode 58 | while(WDT->STATUS.bit.SYNCBUSY); // Wait for synchronization 59 | 60 | _ts = millis(); 61 | } 62 | 63 | void Watchdog::clear() { 64 | if (!WDT->STATUS.bit.SYNCBUSY && millis() - _ts >= 300) { 65 | REG_WDT_CLEAR = WDT_CLEAR_CLEAR_KEY; 66 | _ts = millis(); 67 | } 68 | } 69 | 70 | extern Watchdog Watchdog; 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /examples/IonoMkrLoRaWAN/CayenneLPP.h: -------------------------------------------------------------------------------- 1 | // Adapted from https://developer.mbed.org/teams/myDevicesIoT/code/Cayenne-LPP/ 2 | 3 | // Copyright © 2017 The Things Network 4 | // Use of this source code is governed by the MIT license that can be found in the LICENSE file. 5 | 6 | #ifndef _CAYENNE_LPP_H_ 7 | #define _CAYENNE_LPP_H_ 8 | 9 | #include 10 | 11 | //LPP_BATTERY = // TODO Unsupported in IPSO Smart Object 12 | //LPP_PROXIMITY = // TODO Unsupported in IPSO Smart Object 13 | 14 | #define LPP_DIGITAL_INPUT 0 // 1 byte 15 | #define LPP_DIGITAL_OUTPUT 1 // 1 byte 16 | #define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed 17 | #define LPP_ANALOG_OUTPUT 3 // 2 bytes, 0.01 signed 18 | #define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned 19 | #define LPP_PRESENCE 102 // 1 byte, 1 20 | #define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed 21 | #define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned 22 | #define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G 23 | #define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1 hPa Unsigned 24 | #define LPP_UNIXTIME 133 // 4 bytes, unsigned uint_32_t 25 | #define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 °/s 26 | #define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter 27 | 28 | // Data ID + Data Type + Data Size 29 | #define LPP_DIGITAL_INPUT_SIZE 3 // 1 byte 30 | #define LPP_DIGITAL_OUTPUT_SIZE 3 // 1 byte 31 | #define LPP_ANALOG_INPUT_SIZE 4 // 2 bytes, 0.01 signed 32 | #define LPP_ANALOG_OUTPUT_SIZE 4 // 2 bytes, 0.01 signed 33 | #define LPP_LUMINOSITY_SIZE 4 // 2 bytes, 1 lux unsigned 34 | #define LPP_PRESENCE_SIZE 3 // 1 byte, 1 35 | #define LPP_TEMPERATURE_SIZE 4 // 2 bytes, 0.1°C signed 36 | #define LPP_RELATIVE_HUMIDITY_SIZE 3 // 1 byte, 0.5% unsigned 37 | #define LPP_ACCELEROMETER_SIZE 8 // 2 bytes per axis, 0.001G 38 | #define LPP_BAROMETRIC_PRESSURE_SIZE 4 // 2 bytes 0.1 hPa Unsigned 39 | #define LPP_UNIXTIME_SIZE 6 // 4 bytes, unsigned uint_32_t 40 | #define LPP_GYROMETER_SIZE 8 // 2 bytes per axis, 0.01 °/s 41 | #define LPP_GPS_SIZE 11 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter 42 | 43 | class CayenneLPP 44 | { 45 | public: 46 | CayenneLPP(uint8_t size); 47 | ~CayenneLPP(); 48 | 49 | void reset(void); 50 | uint8_t getSize(void); 51 | uint8_t *getBuffer(void); 52 | uint8_t copy(uint8_t *buffer); 53 | 54 | uint8_t addDigitalInput(uint8_t channel, uint8_t value); 55 | uint8_t addDigitalOutput(uint8_t channel, uint8_t value); 56 | 57 | uint8_t addAnalogInput(uint8_t channel, float value); 58 | uint8_t addAnalogOutput(uint8_t channel, float value); 59 | 60 | uint8_t addLuminosity(uint8_t channel, uint16_t lux); 61 | uint8_t addPresence(uint8_t channel, uint8_t value); 62 | uint8_t addTemperature(uint8_t channel, float celsius); 63 | uint8_t addRelativeHumidity(uint8_t channel, float rh); 64 | uint8_t addAccelerometer(uint8_t channel, float x, float y, float z); 65 | uint8_t addBarometricPressure(uint8_t channel, float hpa); 66 | uint8_t addUnixTime(uint8_t channel, uint32_t unixtime); 67 | uint8_t addGyrometer(uint8_t channel, float x, float y, float z); 68 | uint8_t addGPS(uint8_t channel, float latitude, float longitude, float meters); 69 | 70 | private: 71 | uint8_t *buffer; 72 | uint8_t maxsize; 73 | uint8_t cursor; 74 | }; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /examples/IonoIO/IonoIO.ino: -------------------------------------------------------------------------------- 1 | /* 2 | IonoIO.ino - Using Iono's I/O 3 | 4 | Copyright (C) 2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include 17 | 18 | unsigned long printTs; 19 | 20 | void setup() { 21 | Serial.begin(9600); 22 | while (!Serial); 23 | 24 | Iono.setup(); 25 | 26 | // Set callback function on AV1 state change 27 | // with 500 ms min stable time and 200 mV min variation 28 | Iono.subscribeAnalog(AV1, 500, 0.2, onAV1change); 29 | 30 | // Set callback function on AI2 state change 31 | // with 300 ms min stable time and 1 mA min variation 32 | Iono.subscribeAnalog(AI2, 300, 1, onAI2change); 33 | 34 | // Set same callback function on DI3 and DI4 state change 35 | // with 100 and 200 ms debounce 36 | Iono.subscribeDigital(DI3, 100, onDebounce); 37 | Iono.subscribeDigital(DI4, 200, onDebounce); 38 | 39 | // Flip DO2 on every low-to-high transition of DI3 40 | // after a 100 ms debounce (must be same as subscribe) 41 | Iono.linkDiDo(DI3, DO2, LINK_FLIP_H, 100); 42 | 43 | // If DI5 and/or DI6 are used as TTL lines (jumper in BYP position) 44 | // call setBypass() and set their pin mode 45 | Iono.setBypass(DI5, INPUT); 46 | Iono.setBypass(DI6, OUTPUT); 47 | 48 | Serial.println("I/O setup done."); 49 | } 50 | 51 | void loop() { 52 | // Call Iono.process() with intervals smaller than 53 | // any debounce or stable times set with any subscribe 54 | // or link functions 55 | Iono.process(); 56 | 57 | if (millis() - printTs > 2000) { 58 | Iono.flip(DO1); 59 | 60 | Iono.flip(DI6); 61 | 62 | if (Iono.read(DI3) == HIGH) { 63 | Iono.write(DO3, HIGH); 64 | Iono.write(AO1, 5); 65 | } else { 66 | Iono.write(DO3, LOW); 67 | Iono.write(AO1, 0.5); 68 | } 69 | 70 | Serial.println("-------------"); 71 | 72 | Serial.print("AV1 = "); 73 | Serial.print(Iono.read(AV1)); 74 | Serial.println(" V"); 75 | 76 | Serial.print("AI2 = "); 77 | Serial.print(Iono.read(AI2)); 78 | Serial.println(" mA"); 79 | 80 | Serial.print("DI3 = "); 81 | Serial.println(Iono.read(DI3) == HIGH ? "high" : "low"); 82 | 83 | Serial.print("DI4 = "); 84 | Serial.println(Iono.read(DI4) == HIGH ? "high" : "low"); 85 | 86 | Serial.print("DO1 = "); 87 | Serial.println(Iono.read(DO1) == HIGH ? "high" : "low"); 88 | 89 | Serial.print("DI5 = "); 90 | Serial.println(Iono.read(DI5) == HIGH ? "high" : "low"); 91 | 92 | Serial.print("DI6 = "); 93 | Serial.println(Iono.read(DI6) == HIGH ? "high" : "low"); 94 | 95 | printTs = millis(); 96 | } 97 | 98 | delay(10); 99 | } 100 | 101 | void onAV1change(uint8_t pin, float val) { 102 | Serial.print("AV1 change = "); 103 | Serial.print(val); 104 | Serial.println(" V"); 105 | } 106 | 107 | void onAI2change(uint8_t pin, float val) { 108 | Serial.print("AI2 change = "); 109 | Serial.print(val); 110 | Serial.println(" mA"); 111 | } 112 | 113 | void onDebounce(uint8_t pin, float val) { 114 | if (pin == DI3) { 115 | Serial.print("DI3"); 116 | } else if (pin == DI4) { 117 | Serial.print("DI4"); 118 | } 119 | Serial.print(" debounce = "); 120 | Serial.println(val == HIGH ? "high" : "low"); 121 | } 122 | -------------------------------------------------------------------------------- /examples/WebApp/iono-min.html: -------------------------------------------------------------------------------- 1 | iono
IONO
Saving configuration, please wait...

ok



V
set

2 | -------------------------------------------------------------------------------- /src/Iono.h: -------------------------------------------------------------------------------- 1 | /* 2 | Iono.h - Arduino library for Iono Uno/MKR/RP 3 | 4 | Copyright (C) 2014-2025 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #ifndef Iono_h 17 | #define Iono_h 18 | 19 | #if defined(ARDUINO) && ARDUINO >= 100 20 | #include "Arduino.h" 21 | #else 22 | #include "WProgram.h" 23 | #endif 24 | 25 | #if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_ARCH_RENESAS_UNO) 26 | #define IONO_ARDUINO 1 27 | #define IONO_UNO 1 28 | #elif defined(ARDUINO_ARCH_RP2040) 29 | #define IONO_RP 1 30 | #else 31 | #define IONO_MKR 1 32 | #endif 33 | 34 | #define DO1 0 35 | #define DO2 1 36 | #define DO3 2 37 | #define DO4 3 38 | #define DO5 4 39 | #define DO6 5 40 | 41 | #define DI1 6 42 | #define AV1 7 43 | #define AI1 8 44 | 45 | #define DI2 9 46 | #define AV2 10 47 | #define AI2 11 48 | 49 | #define DI3 12 50 | #define AV3 13 51 | #define AI3 14 52 | 53 | #define DI4 15 54 | #define AV4 16 55 | #define AI4 17 56 | 57 | #define DI5 18 58 | #define DI6 19 59 | #define AO1 20 60 | 61 | #ifdef IONO_UNO 62 | #define IONO_PIN_DO1 A4 63 | #define IONO_PIN_DO2 A5 64 | #define IONO_PIN_DO3 5 65 | #define IONO_PIN_DO4 6 66 | #define IONO_PIN_DO5 7 67 | #define IONO_PIN_DO6 8 68 | #define DO_IDX_MAX 6 69 | 70 | #define IONO_PIN_DI1 A0 71 | #define IONO_PIN_AV1 A0 72 | #define IONO_PIN_AI1 A0 73 | 74 | #define IONO_PIN_DI2 A1 75 | #define IONO_PIN_AV2 A1 76 | #define IONO_PIN_AI2 A1 77 | 78 | #define IONO_PIN_DI3 A2 79 | #define IONO_PIN_AV3 A2 80 | #define IONO_PIN_AI3 A2 81 | 82 | #define IONO_PIN_DI4 A3 83 | #define IONO_PIN_AV4 A3 84 | #define IONO_PIN_AI4 A3 85 | 86 | #define IONO_PIN_DI5 2 87 | #define IONO_PIN_DI6 3 88 | #define IONO_PIN_DI5_BYP 2 89 | #define IONO_PIN_DI6_BYP 3 90 | 91 | #define IONO_PIN_AO1 9 92 | #elif defined(IONO_RP) 93 | #define IONO_PIN_DO1 13 94 | #define IONO_PIN_DO2 12 95 | #define IONO_PIN_DO3 11 96 | #define IONO_PIN_DO4 10 97 | #define DO_IDX_MAX 4 98 | 99 | #define IONO_PIN_DI1 26 100 | #define IONO_PIN_AV1 26 101 | #define IONO_PIN_AI1 26 102 | 103 | #define IONO_PIN_DI2 27 104 | #define IONO_PIN_AV2 27 105 | #define IONO_PIN_AI2 27 106 | 107 | #define IONO_PIN_DI3 28 108 | #define IONO_PIN_AV3 28 109 | #define IONO_PIN_AI3 28 110 | 111 | #define IONO_PIN_DI4 29 112 | #define IONO_PIN_AV4 29 113 | #define IONO_PIN_AI4 29 114 | 115 | #define IONO_PIN_DI5 24 116 | #define IONO_PIN_DI6 23 117 | #define IONO_PIN_DI5_BYP 7 118 | #define IONO_PIN_DI6_BYP 6 119 | 120 | #define IONO_PIN_AO1 8 121 | #else 122 | #define IONO_PIN_DO1 3 123 | #define IONO_PIN_DO2 2 124 | #define IONO_PIN_DO3 A6 125 | #define IONO_PIN_DO4 A5 126 | #define DO_IDX_MAX 4 127 | 128 | #define IONO_PIN_DI1 A1 129 | #define IONO_PIN_AV1 A1 130 | #define IONO_PIN_AI1 A1 131 | 132 | #define IONO_PIN_DI2 A2 133 | #define IONO_PIN_AV2 A2 134 | #define IONO_PIN_AI2 A2 135 | 136 | #define IONO_PIN_DI3 A3 137 | #define IONO_PIN_AV3 A3 138 | #define IONO_PIN_AI3 A3 139 | 140 | #define IONO_PIN_DI4 A4 141 | #define IONO_PIN_AV4 A4 142 | #define IONO_PIN_AI4 A4 143 | 144 | #define IONO_PIN_DI5 7 145 | #define IONO_PIN_DI6 5 146 | #define IONO_PIN_DI5_BYP 0 147 | #define IONO_PIN_DI6_BYP 1 148 | 149 | #define IONO_PIN_AO1 A0 150 | #endif 151 | 152 | #ifdef IONO_MKR 153 | #define PIN_TXEN 4 154 | #endif 155 | 156 | #ifdef IONO_RP 157 | #define PIN_TXEN_N 25 158 | #endif 159 | 160 | #ifndef SERIAL_PORT_MONITOR 161 | #define SERIAL_PORT_MONITOR Serial 162 | #endif 163 | 164 | #ifndef SERIAL_PORT_HARDWARE 165 | #define SERIAL_PORT_HARDWARE Serial1 166 | #endif 167 | 168 | #define IONO_RS485 SERIAL_PORT_HARDWARE 169 | 170 | #define LINK_FOLLOW 1 171 | #define LINK_INVERT 2 172 | #define LINK_FLIP_T 3 173 | #define LINK_FLIP_H 4 174 | #define LINK_FLIP_L 5 175 | 176 | #if (ARDUINO_API_VERSION >= 10000) 177 | typedef PinMode iono_pin_mode_t; 178 | #else 179 | typedef int iono_pin_mode_t; 180 | #endif 181 | 182 | class IonoClass 183 | { 184 | public: 185 | typedef void Callback(uint8_t pin, float value); 186 | IonoClass(); 187 | #if defined(IONO_MKR) || defined(IONO_RP) 188 | void setBYP(uint8_t pin, bool value); 189 | #endif 190 | void setBypass(uint8_t pin, iono_pin_mode_t mode); 191 | void setup(); 192 | float read(uint8_t pin); 193 | float readAnalogAvg(uint8_t pin, int n); 194 | void write(uint8_t pin, float value); 195 | void flip(uint8_t pin); 196 | void subscribeDigital(uint8_t pin, unsigned long stableTime, Callback *callback); 197 | void subscribeAnalog(uint8_t pin, unsigned long stableTime, float minVariation, Callback *callback); 198 | void linkDiDo(uint8_t dix, uint8_t dox, uint8_t mode, unsigned long stableTime); 199 | void process(); 200 | void serialTxEn(bool enabled); 201 | 202 | private: 203 | uint8_t _pinMap[21]; 204 | float _ao1_val; 205 | typedef struct CallbackMap 206 | { 207 | uint8_t pin; 208 | unsigned long stableTime; 209 | float minVariation; 210 | Callback *callback; 211 | uint8_t linkedPin; 212 | uint8_t linkMode; 213 | float value; 214 | unsigned long lastTS; 215 | } CallbackMap; 216 | CallbackMap _i1; 217 | CallbackMap _i2; 218 | CallbackMap _i3; 219 | CallbackMap _i4; 220 | CallbackMap _i5; 221 | CallbackMap _i6; 222 | CallbackMap _o1; 223 | CallbackMap _o2; 224 | CallbackMap _o3; 225 | CallbackMap _o4; 226 | CallbackMap _o5; 227 | CallbackMap _o6; 228 | CallbackMap _a1; 229 | 230 | void check(CallbackMap *input); 231 | }; 232 | 233 | extern IonoClass Iono; 234 | 235 | #endif 236 | -------------------------------------------------------------------------------- /src/IonoUDP.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | IonoUDP.cpp - Arduino library for the control of Iono Uno Ethernet via a simple UDP protocol 3 | 4 | Copyright (C) 2014-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include "IonoUDP.h" 17 | 18 | char IonoUDPClass::_pinName[][4] = { 19 | "DO1", 20 | "DO2", 21 | "DO3", 22 | "DO4", 23 | "DO5", 24 | "DO6", 25 | "DI1", 26 | "AV1", 27 | "AI1", 28 | "DI2", 29 | "AV2", 30 | "AI2", 31 | "DI3", 32 | "AV3", 33 | "AI3", 34 | "DI4", 35 | "AV4", 36 | "AI4", 37 | "DI5", 38 | "DI6", 39 | "AO1" 40 | }; 41 | 42 | IonoUDPClass::IonoUDPClass() { 43 | _ipBroadcast = IPAddress(255, 255, 255, 255); 44 | 45 | for (int i = 0; i < 20; i++) { 46 | _lastValue[i] = -99; 47 | _value[i] = -99; 48 | _lastTS[i] = -99; 49 | } 50 | 51 | _progr = 0; 52 | _lastSend = 0; 53 | } 54 | 55 | void IonoUDPClass::begin(const char *id, EthernetUDP Udp, unsigned int port, unsigned long stableTime, float minVariation) { 56 | _id = id; 57 | _Udp = Udp; 58 | _port = port; 59 | _stableTime = stableTime; 60 | _minVariation = minVariation; 61 | Iono.setup(); 62 | } 63 | 64 | void IonoUDPClass::process() { 65 | checkState(); 66 | checkCommands(); 67 | } 68 | 69 | void IonoUDPClass::checkState() { 70 | check(DO1); 71 | check(DO2); 72 | check(DO3); 73 | check(DO4); 74 | check(DO5); 75 | check(DO6); 76 | 77 | check(DI1); 78 | check(DI2); 79 | check(DI3); 80 | check(DI4); 81 | check(DI5); 82 | check(DI6); 83 | 84 | check(AV1); 85 | check(AV2); 86 | check(AV3); 87 | check(AV4); 88 | 89 | check(AI1); 90 | check(AI2); 91 | check(AI3); 92 | check(AI4); 93 | 94 | unsigned long ts = millis(); 95 | if (ts - _lastSend > 30000) { 96 | for (int i = 0; i < 3; i++) { 97 | _Udp.beginPacket(_ipBroadcast, _port); 98 | _Udp.write("{\"id\":\""); 99 | _Udp.write(_id); 100 | _Udp.write("\",\"pr\":"); 101 | _Udp.write('0' + _progr); 102 | _Udp.write("}"); 103 | _Udp.endPacket(); 104 | delay(3); 105 | } 106 | 107 | _progr = (_progr + 1) % 10; 108 | _lastSend = ts; 109 | } 110 | } 111 | 112 | void IonoUDPClass::check(int pin) { 113 | float val = Iono.read(pin); 114 | unsigned long ts = millis(); 115 | 116 | if (val != _lastValue[pin]) { 117 | _lastTS[pin] = ts; 118 | } 119 | 120 | if ((ts - _lastTS[pin]) >= _stableTime) { 121 | if (val != _value[pin]) { 122 | float diff = _value[pin] - val; 123 | diff = abs(diff); 124 | if (diff >= _minVariation) { 125 | _value[pin] = val; 126 | send(pin, val); 127 | _lastSend = ts; 128 | } 129 | } 130 | } 131 | 132 | _lastValue[pin] = val; 133 | } 134 | 135 | void IonoUDPClass::send(int pin, float val) { 136 | char sVal[6]; 137 | if (_pinName[pin][0] == 'D') { 138 | sVal[0] = val == HIGH ? '1' : '0'; 139 | sVal[1] = '\0'; 140 | } else { 141 | ftoa(sVal, val); 142 | } 143 | 144 | for (int i = 0; i < 3; i++) { 145 | _Udp.beginPacket(_ipBroadcast, _port); 146 | _Udp.write("{\"id\":\""); 147 | _Udp.write(_id); 148 | _Udp.write("\",\"pin\":\""); 149 | _Udp.write(_pinName[pin]); 150 | _Udp.write("\",\"pr\":"); 151 | _Udp.write('0' + _progr); 152 | _Udp.write(",\"val\":"); 153 | _Udp.write(sVal); 154 | _Udp.write("}"); 155 | _Udp.endPacket(); 156 | delay(3); 157 | } 158 | 159 | _progr = (_progr + 1) % 10; 160 | } 161 | 162 | void IonoUDPClass::ftoa(char *sVal, float fVal) { 163 | fVal += 0.005; 164 | 165 | int dVal = fVal; 166 | int dec = (int)(fVal * 100) % 100; 167 | 168 | int i = 0; 169 | int d = dVal / 10; 170 | if (d != 0) { 171 | sVal[i++] = d + '0'; 172 | } 173 | sVal[i++] = (dVal % 10) + '0'; 174 | sVal[i++] = '.'; 175 | sVal[i++] = (dec / 10) + '0'; 176 | sVal[i++] = (dec % 10) + '0'; 177 | sVal[i] = '\0'; 178 | } 179 | 180 | void IonoUDPClass::checkCommands() { 181 | int packetSize = _Udp.parsePacket(); 182 | if(packetSize) { 183 | for (int i = 0; i < COMMAND_MAX_SIZE; i++) { 184 | _command[i] = '\0'; 185 | } 186 | _Udp.read(_command, COMMAND_MAX_SIZE); 187 | if (strcmp(_command, "state") == 0) { 188 | _Udp.beginPacket(_Udp.remoteIP(), _Udp.remotePort()); 189 | _Udp.write("{"); 190 | _Udp.write("\"id\":\""); 191 | _Udp.write(_id); 192 | _Udp.write("\""); 193 | char sVal[6]; 194 | for (int i = 0; i < 20; i++) { 195 | _Udp.write(","); 196 | _Udp.write("\""); 197 | _Udp.write(_pinName[i]); 198 | _Udp.write("\":"); 199 | if (_pinName[i][0] == 'D') { 200 | _Udp.write(_value[i] == HIGH ? "1" : "0"); 201 | } else { 202 | ftoa(sVal, _value[i]); 203 | _Udp.write(sVal); 204 | } 205 | } 206 | _Udp.write("}"); 207 | _Udp.endPacket(); 208 | return; 209 | 210 | } else if (strlen(_command) > 4 && _command[3] == '=') { 211 | char pn[4]; 212 | for (int i = 0; i < 3; i++) { 213 | pn[i] = _command[i]; 214 | } 215 | pn[3] = '\0'; 216 | 217 | int pin = -1; 218 | for (int i = 0; i < 21; i++) { 219 | if (strcmp(_pinName[i], pn) == 0) { 220 | pin = i; 221 | break; 222 | } 223 | } 224 | 225 | if (pin != -1) { 226 | if (pn[0] == 'D') { 227 | if (_command[4] == 'f') { 228 | Iono.flip(pin); 229 | } else { 230 | Iono.write(pin, _command[4] == '1' ? HIGH : LOW); 231 | } 232 | 233 | } else { 234 | char val[6]; 235 | for (int i = 0; i < 5; i++) { 236 | val[i] = _command[i + 4]; 237 | } 238 | val[5] = '\0'; 239 | Iono.write(pin, atof(val)); 240 | } 241 | } 242 | 243 | _Udp.beginPacket(_Udp.remoteIP(), _Udp.remotePort()); 244 | _Udp.write("ok"); 245 | _Udp.endPacket(); 246 | return; 247 | } 248 | 249 | _Udp.beginPacket(_Udp.remoteIP(), _Udp.remotePort()); 250 | _Udp.write("error"); 251 | _Udp.endPacket(); 252 | } 253 | } 254 | 255 | IonoUDPClass IonoUDP; 256 | -------------------------------------------------------------------------------- /examples/IonoMkrLoRaWAN/CayenneLPP.cpp: -------------------------------------------------------------------------------- 1 | // Adapted from https://developer.mbed.org/teams/myDevicesIoT/code/Cayenne-LPP/ 2 | 3 | // Copyright © 2017 The Things Network 4 | // Use of this source code is governed by the MIT license that can be found in the LICENSE file. 5 | 6 | #include "CayenneLPP.h" 7 | 8 | CayenneLPP::CayenneLPP(uint8_t size) : maxsize(size) 9 | { 10 | buffer = (uint8_t *)malloc(size); 11 | cursor = 0; 12 | } 13 | 14 | CayenneLPP::~CayenneLPP(void) 15 | { 16 | free(buffer); 17 | } 18 | 19 | void CayenneLPP::reset(void) 20 | { 21 | cursor = 0; 22 | } 23 | 24 | uint8_t CayenneLPP::getSize(void) 25 | { 26 | return cursor; 27 | } 28 | 29 | uint8_t *CayenneLPP::getBuffer(void) 30 | { 31 | // uint8_t[cursor] result; 32 | // memcpy(result, buffer, cursor); 33 | // return result; 34 | return buffer; 35 | } 36 | 37 | uint8_t CayenneLPP::copy(uint8_t *dst) 38 | { 39 | memcpy(dst, buffer, cursor); 40 | return cursor; 41 | } 42 | 43 | uint8_t CayenneLPP::addDigitalInput(uint8_t channel, uint8_t value) 44 | { 45 | if ((cursor + LPP_DIGITAL_INPUT_SIZE) > maxsize) 46 | { 47 | return 0; 48 | } 49 | buffer[cursor++] = channel; 50 | buffer[cursor++] = LPP_DIGITAL_INPUT; 51 | buffer[cursor++] = value; 52 | 53 | return cursor; 54 | } 55 | 56 | uint8_t CayenneLPP::addDigitalOutput(uint8_t channel, uint8_t value) 57 | { 58 | if ((cursor + LPP_DIGITAL_OUTPUT_SIZE) > maxsize) 59 | { 60 | return 0; 61 | } 62 | buffer[cursor++] = channel; 63 | buffer[cursor++] = LPP_DIGITAL_OUTPUT; 64 | buffer[cursor++] = value; 65 | 66 | return cursor; 67 | } 68 | 69 | uint8_t CayenneLPP::addAnalogInput(uint8_t channel, float value) 70 | { 71 | if ((cursor + LPP_ANALOG_INPUT_SIZE) > maxsize) 72 | { 73 | return 0; 74 | } 75 | 76 | int16_t val = value * 100; 77 | buffer[cursor++] = channel; 78 | buffer[cursor++] = LPP_ANALOG_INPUT; 79 | buffer[cursor++] = val >> 8; 80 | buffer[cursor++] = val; 81 | 82 | return cursor; 83 | } 84 | 85 | uint8_t CayenneLPP::addAnalogOutput(uint8_t channel, float value) 86 | { 87 | if ((cursor + LPP_ANALOG_OUTPUT_SIZE) > maxsize) 88 | { 89 | return 0; 90 | } 91 | int16_t val = value * 100; 92 | buffer[cursor++] = channel; 93 | buffer[cursor++] = LPP_ANALOG_OUTPUT; 94 | buffer[cursor++] = val >> 8; 95 | buffer[cursor++] = val; 96 | 97 | return cursor; 98 | } 99 | 100 | uint8_t CayenneLPP::addLuminosity(uint8_t channel, uint16_t lux) 101 | { 102 | if ((cursor + LPP_LUMINOSITY_SIZE) > maxsize) 103 | { 104 | return 0; 105 | } 106 | buffer[cursor++] = channel; 107 | buffer[cursor++] = LPP_LUMINOSITY; 108 | buffer[cursor++] = lux >> 8; 109 | buffer[cursor++] = lux; 110 | 111 | return cursor; 112 | } 113 | 114 | uint8_t CayenneLPP::addPresence(uint8_t channel, uint8_t value) 115 | { 116 | if ((cursor + LPP_PRESENCE_SIZE) > maxsize) 117 | { 118 | return 0; 119 | } 120 | buffer[cursor++] = channel; 121 | buffer[cursor++] = LPP_PRESENCE; 122 | buffer[cursor++] = value; 123 | 124 | return cursor; 125 | } 126 | 127 | uint8_t CayenneLPP::addTemperature(uint8_t channel, float celsius) 128 | { 129 | if ((cursor + LPP_TEMPERATURE_SIZE) > maxsize) 130 | { 131 | return 0; 132 | } 133 | int16_t val = celsius * 10; 134 | buffer[cursor++] = channel; 135 | buffer[cursor++] = LPP_TEMPERATURE; 136 | buffer[cursor++] = val >> 8; 137 | buffer[cursor++] = val; 138 | 139 | return cursor; 140 | } 141 | 142 | uint8_t CayenneLPP::addRelativeHumidity(uint8_t channel, float rh) 143 | { 144 | if ((cursor + LPP_RELATIVE_HUMIDITY_SIZE) > maxsize) 145 | { 146 | return 0; 147 | } 148 | buffer[cursor++] = channel; 149 | buffer[cursor++] = LPP_RELATIVE_HUMIDITY; 150 | buffer[cursor++] = rh * 2; 151 | 152 | return cursor; 153 | } 154 | 155 | uint8_t CayenneLPP::addAccelerometer(uint8_t channel, float x, float y, float z) 156 | { 157 | if ((cursor + LPP_ACCELEROMETER_SIZE) > maxsize) 158 | { 159 | return 0; 160 | } 161 | int16_t vx = x * 1000; 162 | int16_t vy = y * 1000; 163 | int16_t vz = z * 1000; 164 | 165 | buffer[cursor++] = channel; 166 | buffer[cursor++] = LPP_ACCELEROMETER; 167 | buffer[cursor++] = vx >> 8; 168 | buffer[cursor++] = vx; 169 | buffer[cursor++] = vy >> 8; 170 | buffer[cursor++] = vy; 171 | buffer[cursor++] = vz >> 8; 172 | buffer[cursor++] = vz; 173 | 174 | return cursor; 175 | } 176 | 177 | uint8_t CayenneLPP::addBarometricPressure(uint8_t channel, float hpa) 178 | { 179 | if ((cursor + LPP_BAROMETRIC_PRESSURE_SIZE) > maxsize) 180 | { 181 | return 0; 182 | } 183 | int16_t val = hpa * 10; 184 | 185 | buffer[cursor++] = channel; 186 | buffer[cursor++] = LPP_BAROMETRIC_PRESSURE; 187 | buffer[cursor++] = val >> 8; 188 | buffer[cursor++] = val; 189 | 190 | return cursor; 191 | } 192 | 193 | uint8_t CayenneLPP::addUnixTime(uint8_t channel, uint32_t unixtime) 194 | { 195 | if ((cursor + LPP_UNIXTIME_SIZE) > maxsize) 196 | { 197 | return 0; 198 | } 199 | int32_t val = unixtime; 200 | 201 | buffer[cursor++] = channel; 202 | buffer[cursor++] = LPP_UNIXTIME; 203 | buffer[cursor++] = val >> 24; 204 | buffer[cursor++] = val >> 16; 205 | buffer[cursor++] = val >> 8; 206 | buffer[cursor++] = val; 207 | 208 | return cursor; 209 | } 210 | 211 | uint8_t CayenneLPP::addGyrometer(uint8_t channel, float x, float y, float z) 212 | { 213 | if ((cursor + LPP_GYROMETER_SIZE) > maxsize) 214 | { 215 | return 0; 216 | } 217 | int16_t vx = x * 100; 218 | int16_t vy = y * 100; 219 | int16_t vz = z * 100; 220 | 221 | buffer[cursor++] = channel; 222 | buffer[cursor++] = LPP_GYROMETER; 223 | buffer[cursor++] = vx >> 8; 224 | buffer[cursor++] = vx; 225 | buffer[cursor++] = vy >> 8; 226 | buffer[cursor++] = vy; 227 | buffer[cursor++] = vz >> 8; 228 | buffer[cursor++] = vz; 229 | 230 | return cursor; 231 | } 232 | 233 | uint8_t CayenneLPP::addGPS(uint8_t channel, float latitude, float longitude, float meters) 234 | { 235 | if ((cursor + LPP_GPS_SIZE) > maxsize) 236 | { 237 | return 0; 238 | } 239 | int32_t lat = latitude * 10000; 240 | int32_t lon = longitude * 10000; 241 | int32_t alt = meters * 100; 242 | 243 | buffer[cursor++] = channel; 244 | buffer[cursor++] = LPP_GPS; 245 | 246 | buffer[cursor++] = lat >> 16; 247 | buffer[cursor++] = lat >> 8; 248 | buffer[cursor++] = lat; 249 | buffer[cursor++] = lon >> 16; 250 | buffer[cursor++] = lon >> 8; 251 | buffer[cursor++] = lon; 252 | buffer[cursor++] = alt >> 16; 253 | buffer[cursor++] = alt >> 8; 254 | buffer[cursor++] = alt; 255 | 256 | return cursor; 257 | } 258 | -------------------------------------------------------------------------------- /examples/IonoMkrLoRaWAN/IonoMkrLoRaWAN.ino: -------------------------------------------------------------------------------- 1 | /* 2 | IonoMkrLoRaWAN.cpp 3 | 4 | Copyright (C) 2019-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any nlater version. 13 | See file LICENSE.txt for further informations on licesing terms. 14 | */ 15 | 16 | #include 17 | #include "MKRWAN.h" 18 | #include "SerialConfig.h" 19 | #include "Watchdog.h" 20 | #include "CayenneLPP.h" 21 | 22 | #define DEBOUNCE_MS 25 23 | 24 | LoRaModem modem; 25 | CayenneLPP lpp(100); 26 | 27 | uint8_t in1; 28 | uint8_t in2; 29 | uint8_t in3; 30 | uint8_t in4; 31 | uint8_t rcvBuf[16]; 32 | float lastSentIn[] = {-1, -1, -1, -1, -1, -1}; 33 | uint16_t lastSentCount[] = {0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}; 34 | uint16_t valCount[] = {0, 0, 0, 0, 0, 0}; 35 | uint8_t lastSentOut[] = {0xff, 0xff, 0xff, 0xff}; 36 | float lastSentAO1 = -1; 37 | unsigned long lastUpdateSendTs; 38 | unsigned long lastFullStateSendTs; 39 | unsigned long lastHeartbeatSendTs; 40 | bool needToSend = false; 41 | bool initialized = false; 42 | 43 | void setup() { 44 | pinMode(LED_BUILTIN, OUTPUT); 45 | digitalWrite(LED_BUILTIN, HIGH); 46 | SerialConfig.setup(); 47 | while (!SerialConfig.isConfigured) { 48 | SerialConfig.process(); 49 | } 50 | 51 | if (!initialize()) { 52 | delay(4000); 53 | digitalWrite(LED_BUILTIN, LOW); 54 | delay(1000); 55 | NVIC_SystemReset(); 56 | } 57 | 58 | lastUpdateSendTs = lastFullStateSendTs = lastHeartbeatSendTs = millis(); 59 | digitalWrite(LED_BUILTIN, LOW); 60 | 61 | // Sleep random time up to 7 seconds 62 | uint32_t seed = 0; 63 | for (int i = 0; i < 32; i++) { 64 | seed <<= 1; 65 | seed |= analogRead(9) & 1; 66 | } 67 | randomSeed(seed); 68 | delay(random(7000)); 69 | } 70 | 71 | void checkDataRate(bool force = false) { 72 | static unsigned long lastTs = 0; 73 | int dr; 74 | unsigned long now = millis(); 75 | if (force || now - lastTs >= 5000) { 76 | lastTs = now; 77 | dr = modem.getDataRate(); 78 | if (dr >= 0) { 79 | if (dr != SerialConfig.dataRate) { 80 | for (int i = 0; i < 3; i++) { 81 | if (modem.dataRate(SerialConfig.dataRate)) { 82 | break; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | void checkFrameCounters() { 91 | static unsigned long lastUpTs = 0; 92 | static unsigned long lastDownTs = 0; 93 | unsigned long now = millis(); 94 | 95 | if (now - lastUpTs >= 300000) { 96 | int64_t fCntUp = modem.getFCU(); 97 | if (fCntUp >= 0) { 98 | if (fCntUp > SerialConfig.fCntUp + 50) { 99 | SerialConfig.writeFCntUp(fCntUp + 50); 100 | SerialConfig.fCntUp = fCntUp; 101 | } 102 | lastUpTs = now; 103 | } 104 | } 105 | 106 | if (now - lastDownTs >= 300000) { 107 | int64_t fCntDown = modem.getFCD(); 108 | if (fCntDown >= 0) { 109 | if (fCntDown > SerialConfig.fCntDown + 20) { 110 | SerialConfig.writeFCntDown(fCntDown); 111 | SerialConfig.fCntDown = fCntDown; 112 | } 113 | lastDownTs = now; 114 | } 115 | } 116 | } 117 | 118 | void loop() { 119 | Iono.process(); 120 | 121 | unsigned long now = millis(); 122 | if (now - lastFullStateSendTs >= 15 * 60000 || now - lastUpdateSendTs >= 7 * 60000) { 123 | if (sendState(true)) { 124 | lastFullStateSendTs = now; 125 | } 126 | } else if (needToSend || now - lastHeartbeatSendTs >= 3 * 60000) { 127 | sendState(false); 128 | } 129 | 130 | while (modem.available()) { 131 | if (modem.read(rcvBuf, 4) == 4 && rcvBuf[3] == 0xff) { 132 | switch (rcvBuf[0]) { 133 | case 101: Iono.write(DO1, rcvBuf[2] == 0 ? LOW : HIGH); lastSentOut[0] = 0xff; break; 134 | case 102: Iono.write(DO2, rcvBuf[2] == 0 ? LOW : HIGH); lastSentOut[1] = 0xff; break; 135 | case 103: Iono.write(DO3, rcvBuf[2] == 0 ? LOW : HIGH); lastSentOut[2] = 0xff; break; 136 | case 104: Iono.write(DO4, rcvBuf[2] == 0 ? LOW : HIGH); lastSentOut[3] = 0xff; break; 137 | case 201: Iono.write(AO1, (((rcvBuf[1] & 0xff) << 8) + rcvBuf[2]) / 100.0); lastSentAO1 = -1; break; 138 | } 139 | needToSend = true; 140 | } 141 | } 142 | 143 | checkDataRate(); 144 | 145 | checkFrameCounters(); 146 | 147 | if (SerialConfig.isAvailable) { 148 | SerialConfig.process(); 149 | } 150 | 151 | Watchdog.clear(); 152 | } 153 | 154 | bool send(uint8_t* data, size_t len) { 155 | modem.beginPacket(); 156 | modem.write(data, len); 157 | return modem.endPacket(false) > 0; 158 | } 159 | 160 | bool sendState(bool sendAll) { 161 | static unsigned long lastTs = 0; 162 | float valIn[6]; 163 | uint8_t valOut[4]; 164 | float valAO1; 165 | 166 | unsigned long now = millis(); 167 | if (now - lastTs < 1500) { 168 | return false; 169 | } 170 | lastTs = now - random(500); 171 | 172 | digitalWrite(LED_BUILTIN, HIGH); 173 | 174 | valIn[0] = Iono.read(in1); 175 | valIn[1] = Iono.read(in2); 176 | valIn[2] = Iono.read(in3); 177 | valIn[3] = Iono.read(in4); 178 | valIn[4] = Iono.read(DI5); 179 | valIn[5] = Iono.read(DI6); 180 | valOut[0] = (uint8_t) Iono.read(DO1); 181 | valOut[1] = (uint8_t) Iono.read(DO2); 182 | valOut[2] = (uint8_t) Iono.read(DO3); 183 | valOut[3] = (uint8_t) Iono.read(DO4); 184 | valAO1 = Iono.read(AO1); 185 | 186 | needToSend = false; 187 | 188 | lpp.reset(); 189 | 190 | lpp.addLuminosity(99, now / 1000); 191 | 192 | for (int i = 0; i < 6; i++) { 193 | if (sendAll || lastSentIn[i] != valIn[i]) { 194 | switch (SerialConfig.modes[i]) { 195 | case 'D': needToSend = true; lpp.addDigitalInput(i + 1, (uint8_t) valIn[i]); break; 196 | case 'V': needToSend = true; lpp.addAnalogInput(i + 11, valIn[i]); break; 197 | case 'I': needToSend = true; lpp.addAnalogInput(i + 21, valIn[i]); break; 198 | default : break; 199 | } 200 | } 201 | } 202 | 203 | for (int i = 0; i < 6; i++) { 204 | if (SerialConfig.modes[i] == 'D') { 205 | if (sendAll || lastSentCount[i] != valCount[i]) { 206 | needToSend = true; 207 | lpp.addAnalogInput(i + 51, valCount[i]); 208 | } 209 | } 210 | } 211 | 212 | for (int i = 0; i < 4; i++) { 213 | if (sendAll || lastSentOut[i] != valOut[i]) { 214 | needToSend = true; 215 | lpp.addDigitalOutput(i + 101, valOut[i]); 216 | } 217 | } 218 | 219 | if (sendAll || lastSentAO1 != valAO1) { 220 | needToSend = true; 221 | lpp.addAnalogOutput(201, valAO1); 222 | } 223 | 224 | if (send(lpp.getBuffer(), lpp.getSize())) { 225 | for (int i = 0; i < 6; i++) { 226 | lastSentIn[i] = valIn[i]; 227 | } 228 | for (int i = 0; i < 6; i++) { 229 | lastSentCount[i] = valCount[i]; 230 | } 231 | for (int i = 0; i < 4; i++) { 232 | lastSentOut[i] = valOut[i]; 233 | } 234 | lastSentAO1 = valAO1; 235 | if (needToSend) { 236 | lastUpdateSendTs = now; 237 | needToSend = false; 238 | } 239 | lastHeartbeatSendTs = now; 240 | } 241 | 242 | digitalWrite(LED_BUILTIN, LOW); 243 | 244 | return !needToSend; 245 | } 246 | 247 | void inputsCallback(uint8_t pin, float value) { 248 | int idx; 249 | if (value == HIGH) { 250 | switch (pin) { 251 | case DI1: idx = 0; break; 252 | case DI2: idx = 1; break; 253 | case DI3: idx = 2; break; 254 | case DI4: idx = 3; break; 255 | case DI5: idx = 4; break; 256 | case DI6: idx = 5; break; 257 | default: idx = -1; break; 258 | } 259 | if (idx >= 0) { 260 | if (valCount[idx] < 327) { 261 | valCount[idx]++; 262 | } else { 263 | valCount[idx] = 0; 264 | } 265 | } 266 | } 267 | 268 | needToSend = true; 269 | } 270 | 271 | bool initialize() { 272 | Watchdog.setup(); 273 | 274 | if (!modem.begin(SerialConfig.band)) { 275 | return false; 276 | } 277 | if (!modem.joinABP(SerialConfig.devAddr, SerialConfig.nwkSKey, SerialConfig.appSKey)) { 278 | return false; 279 | } 280 | if (!modem.dutyCycle(true)) { 281 | return false; 282 | } 283 | if (!modem.setADR(true)) { 284 | return false; 285 | } 286 | if (!modem.configureClass(CLASS_C)) { 287 | return false; 288 | } 289 | if (!modem.setFCU(SerialConfig.fCntUp)) { 290 | return false; 291 | } 292 | if (!modem.setFCD(SerialConfig.fCntDown)) { 293 | return false; 294 | } 295 | checkDataRate(true); 296 | 297 | Iono.subscribeDigital(DO1, 0, &inputsCallback); 298 | Iono.subscribeDigital(DO2, 0, &inputsCallback); 299 | Iono.subscribeDigital(DO3, 0, &inputsCallback); 300 | Iono.subscribeDigital(DO4, 0, &inputsCallback); 301 | 302 | Iono.subscribeAnalog(AO1, 0, 0, &inputsCallback); 303 | 304 | subscribeMultimode(SerialConfig.modes[0], &in1, DI1, AV1, AI1); 305 | subscribeMultimode(SerialConfig.modes[1], &in2, DI2, AV2, AI2); 306 | subscribeMultimode(SerialConfig.modes[2], &in3, DI3, AV3, AI3); 307 | subscribeMultimode(SerialConfig.modes[3], &in4, DI4, AV4, AI4); 308 | subscribeMultimode(SerialConfig.modes[4], NULL, DI5, 0, 0); 309 | subscribeMultimode(SerialConfig.modes[5], NULL, DI6, 0, 0); 310 | 311 | if (SerialConfig.rules[0] != '\0') { 312 | setLink(SerialConfig.modes[0], SerialConfig.rules[0], DI1, DO1); 313 | setLink(SerialConfig.modes[1], SerialConfig.rules[1], DI2, DO2); 314 | setLink(SerialConfig.modes[2], SerialConfig.rules[2], DI3, DO3); 315 | setLink(SerialConfig.modes[3], SerialConfig.rules[3], DI4, DO4); 316 | } 317 | 318 | return true; 319 | } 320 | 321 | void subscribeMultimode(char mode, uint8_t* inx, uint8_t dix, uint8_t avx, uint8_t aix) { 322 | switch (mode) { 323 | case 'D': 324 | if (inx != NULL) { 325 | *inx = dix; 326 | } 327 | Iono.subscribeDigital(dix, DEBOUNCE_MS, &inputsCallback); 328 | break; 329 | case 'V': 330 | if (inx != NULL) { 331 | *inx = avx; 332 | } 333 | Iono.subscribeAnalog(avx, DEBOUNCE_MS, 0.1, &inputsCallback); 334 | break; 335 | case 'I': 336 | if (inx != NULL) { 337 | *inx = aix; 338 | } 339 | Iono.subscribeAnalog(aix, DEBOUNCE_MS, 0.1, &inputsCallback); 340 | break; 341 | default: 342 | break; 343 | } 344 | } 345 | 346 | void setLink(char mode, char rule, uint8_t dix, uint8_t dox) { 347 | if (mode == 'V' || mode == 'I') { 348 | return; 349 | } 350 | switch (rule) { 351 | case 'F': 352 | Iono.linkDiDo(dix, dox, LINK_FOLLOW, DEBOUNCE_MS); 353 | break; 354 | case 'I': 355 | Iono.linkDiDo(dix, dox, LINK_INVERT, DEBOUNCE_MS); 356 | break; 357 | case 'T': 358 | Iono.linkDiDo(dix, dox, LINK_FLIP_T, DEBOUNCE_MS); 359 | break; 360 | case 'H': 361 | Iono.linkDiDo(dix, dox, LINK_FLIP_H, DEBOUNCE_MS); 362 | break; 363 | case 'L': 364 | Iono.linkDiDo(dix, dox, LINK_FLIP_L, DEBOUNCE_MS); 365 | break; 366 | default: 367 | break; 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /src/Iono.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Iono.cpp - Arduino library for Iono Uno/MKR/RP 3 | 4 | Copyright (C) 2014-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include "Iono.h" 17 | 18 | #if defined(IONO_MKR) || defined(ARDUINO_SAMD_ZERO) 19 | #define ANALOG_READ_BITS 12 20 | #define ANALOG_WRITE_BITS 10 21 | #define ANALOG_SET_BITS 1 22 | #elif defined(IONO_RP) 23 | #define ANALOG_READ_BITS 12 24 | #define ANALOG_WRITE_BITS 16 25 | #define ANALOG_SET_BITS 1 26 | #elif defined(ARDUINO_ARCH_RENESAS_UNO) 27 | #define ANALOG_READ_BITS 14 28 | #define ANALOG_WRITE_BITS 12 29 | #define ANALOG_SET_BITS 1 30 | #else 31 | #define ANALOG_READ_BITS 10 32 | #define ANALOG_WRITE_BITS 8 33 | #endif 34 | 35 | #ifdef IONO_UNO 36 | #define IONO_AV_MAX 10.0 37 | #define IONO_AI_MAX 20.0 38 | #else 39 | #define IONO_AV_MAX 30.0 40 | #define IONO_AI_MAX 25.0 41 | #endif 42 | #define IONO_AO_MAX 10.0 43 | 44 | #define ANALOG_READ_MAX ((1 << ANALOG_READ_BITS) - 1) 45 | #define ANALOG_WRITE_MAX ((1 << ANALOG_WRITE_BITS) - 1) 46 | 47 | IonoClass::IonoClass() { 48 | _pinMap[DO1] = IONO_PIN_DO1; 49 | _pinMap[DO2] = IONO_PIN_DO2; 50 | _pinMap[DO3] = IONO_PIN_DO3; 51 | _pinMap[DO4] = IONO_PIN_DO4; 52 | #ifdef IONO_PIN_DO5 53 | _pinMap[DO5] = IONO_PIN_DO5; 54 | #endif 55 | #ifdef IONO_PIN_DO6 56 | _pinMap[DO6] = IONO_PIN_DO6; 57 | #endif 58 | 59 | _pinMap[DI1] = IONO_PIN_DI1; 60 | _pinMap[AV1] = IONO_PIN_AV1; 61 | _pinMap[AI1] = IONO_PIN_AI1; 62 | 63 | _pinMap[DI2] = IONO_PIN_DI2; 64 | _pinMap[AV2] = IONO_PIN_AV2; 65 | _pinMap[AI2] = IONO_PIN_AI2; 66 | 67 | _pinMap[DI3] = IONO_PIN_DI3; 68 | _pinMap[AV3] = IONO_PIN_AV3; 69 | _pinMap[AI3] = IONO_PIN_AI3; 70 | 71 | _pinMap[DI4] = IONO_PIN_DI4; 72 | _pinMap[AV4] = IONO_PIN_AV4; 73 | _pinMap[AI4] = IONO_PIN_AI4; 74 | 75 | _pinMap[DI5] = IONO_PIN_DI5; 76 | _pinMap[DI6] = IONO_PIN_DI6; 77 | _pinMap[AO1] = IONO_PIN_AO1; 78 | 79 | setup(); 80 | } 81 | 82 | void IonoClass::setup() { 83 | pinMode(_pinMap[DO1], OUTPUT); 84 | pinMode(_pinMap[DO2], OUTPUT); 85 | pinMode(_pinMap[DO3], OUTPUT); 86 | pinMode(_pinMap[DO4], OUTPUT); 87 | #ifdef IONO_UNO 88 | pinMode(_pinMap[DO5], OUTPUT); 89 | pinMode(_pinMap[DO6], OUTPUT); 90 | #endif 91 | 92 | pinMode(_pinMap[DI1], INPUT); 93 | pinMode(_pinMap[DI2], INPUT); 94 | pinMode(_pinMap[DI3], INPUT); 95 | pinMode(_pinMap[DI4], INPUT); 96 | 97 | pinMode(_pinMap[DI5], INPUT); 98 | pinMode(_pinMap[DI6], INPUT); 99 | 100 | #ifdef IONO_RP 101 | gpio_disable_pulls(IONO_PIN_DI1); 102 | gpio_disable_pulls(IONO_PIN_DI2); 103 | gpio_disable_pulls(IONO_PIN_DI3); 104 | gpio_disable_pulls(IONO_PIN_DI4); 105 | gpio_disable_pulls(IONO_PIN_DI5); 106 | gpio_disable_pulls(IONO_PIN_DI6); 107 | gpio_disable_pulls(IONO_PIN_DI5_BYP); 108 | gpio_disable_pulls(IONO_PIN_DI6_BYP); 109 | #endif 110 | 111 | #ifdef PIN_TXEN 112 | pinMode(PIN_TXEN, OUTPUT); 113 | #endif 114 | #ifdef PIN_TXEN_N 115 | pinMode(PIN_TXEN_N, OUTPUT); 116 | #endif 117 | 118 | serialTxEn(false); 119 | 120 | #ifdef IONO_RP 121 | IONO_RS485.setRX(17); 122 | IONO_RS485.setTX(16); 123 | // Required for TX LED off 124 | IONO_RS485.begin(9600); 125 | #endif 126 | 127 | // For boards with 5V logic level to use the external 3.3V reference 128 | #if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_AVR_UNO_WIFI_REV2) 129 | analogReference(EXTERNAL); 130 | #elif defined(ARDUINO_ARCH_RENESAS_UNO) 131 | analogReference(AR_EXTERNAL); 132 | #endif 133 | 134 | #ifdef ANALOG_SET_BITS 135 | analogReadResolution(ANALOG_READ_BITS); 136 | analogWriteResolution(ANALOG_WRITE_BITS); 137 | #endif 138 | 139 | #ifdef IONO_RP 140 | analogWriteFreq(1000); 141 | #endif 142 | 143 | write(AO1, 0); 144 | } 145 | 146 | #if defined(IONO_MKR) || defined(IONO_RP) 147 | void IonoClass::setBYP(uint8_t pin, bool value) { 148 | if (pin == DI5) { 149 | _pinMap[DI5] = value ? IONO_PIN_DI5_BYP : IONO_PIN_DI5; 150 | pinMode(_pinMap[DI5], INPUT); 151 | } else if (pin == DI6) { 152 | _pinMap[DI6] = value ? IONO_PIN_DI6_BYP : IONO_PIN_DI6; 153 | pinMode(_pinMap[DI6], INPUT); 154 | } 155 | } 156 | #endif 157 | 158 | void IonoClass::setBypass(uint8_t pin, iono_pin_mode_t mode) { 159 | if (pin == DI5) { 160 | _pinMap[DI5] = IONO_PIN_DI5_BYP; 161 | pinMode(_pinMap[DI5], mode); 162 | } else if (pin == DI6) { 163 | _pinMap[DI6] = IONO_PIN_DI6_BYP; 164 | pinMode(_pinMap[DI6], mode); 165 | } 166 | } 167 | 168 | void IonoClass::subscribeDigital(uint8_t pin, unsigned long stableTime, Callback *callback) { 169 | CallbackMap* input; 170 | 171 | switch (pin) { 172 | case DI1: 173 | input = &_i1; 174 | break; 175 | 176 | case DI2: 177 | input = &_i2; 178 | break; 179 | 180 | case DI3: 181 | input = &_i3; 182 | break; 183 | 184 | case DI4: 185 | input = &_i4; 186 | break; 187 | 188 | case DI5: 189 | input = &_i5; 190 | break; 191 | 192 | case DI6: 193 | input = &_i6; 194 | break; 195 | 196 | case DO1: 197 | input = &_o1; 198 | break; 199 | 200 | case DO2: 201 | input = &_o2; 202 | break; 203 | 204 | case DO3: 205 | input = &_o3; 206 | break; 207 | 208 | case DO4: 209 | input = &_o4; 210 | break; 211 | 212 | case DO5: 213 | input = &_o5; 214 | break; 215 | 216 | case DO6: 217 | input = &_o6; 218 | break; 219 | 220 | default: 221 | return; 222 | } 223 | 224 | (*input).pin = pin; 225 | (*input).stableTime = stableTime; 226 | (*input).minVariation = 0; 227 | (*input).callback = callback; 228 | (*input).value = -1; 229 | (*input).lastTS = millis(); 230 | } 231 | 232 | void IonoClass::subscribeAnalog(uint8_t pin, unsigned long stableTime, float minVariation, Callback *callback) { 233 | CallbackMap* input; 234 | 235 | switch (pin) { 236 | case AV1: 237 | case AI1: 238 | input = &_i1; 239 | break; 240 | 241 | case AV2: 242 | case AI2: 243 | input = &_i2; 244 | break; 245 | 246 | case AV3: 247 | case AI3: 248 | input = &_i3; 249 | break; 250 | 251 | case AV4: 252 | case AI4: 253 | input = &_i4; 254 | break; 255 | 256 | case AO1: 257 | input = &_a1; 258 | break; 259 | 260 | default: 261 | return; 262 | } 263 | 264 | (*input).pin = pin; 265 | (*input).stableTime = stableTime; 266 | (*input).minVariation = minVariation; 267 | (*input).callback = callback; 268 | (*input).value = -100; 269 | (*input).lastTS = millis(); 270 | } 271 | 272 | void IonoClass::linkDiDo(uint8_t dix, uint8_t dox, uint8_t mode, unsigned long stableTime) { 273 | if (dox < DO1 || dox > DO6) { 274 | return; 275 | } 276 | 277 | CallbackMap* input; 278 | 279 | switch (dix) { 280 | case DI1: 281 | input = &_i1; 282 | break; 283 | 284 | case DI2: 285 | input = &_i2; 286 | break; 287 | 288 | case DI3: 289 | input = &_i3; 290 | break; 291 | 292 | case DI4: 293 | input = &_i4; 294 | break; 295 | 296 | case DI5: 297 | input = &_i5; 298 | break; 299 | 300 | case DI6: 301 | input = &_i6; 302 | break; 303 | 304 | default: 305 | return; 306 | } 307 | 308 | (*input).pin = dix; 309 | (*input).stableTime = stableTime; 310 | (*input).minVariation = 0; 311 | (*input).linkedPin = dox; 312 | (*input).linkMode = mode; 313 | (*input).value = -1; 314 | (*input).lastTS = millis(); 315 | } 316 | 317 | void IonoClass::process() { 318 | check(&_i1); 319 | check(&_i2); 320 | check(&_i3); 321 | check(&_i4); 322 | check(&_i5); 323 | check(&_i6); 324 | check(&_o1); 325 | check(&_o2); 326 | check(&_o3); 327 | check(&_o4); 328 | check(&_o5); 329 | check(&_o6); 330 | check(&_a1); 331 | } 332 | 333 | void IonoClass::check(CallbackMap *input) { 334 | if ((*input).callback != NULL || (*input).linkedPin >= 0) { 335 | float val = read((*input).pin); 336 | unsigned long ts = millis(); 337 | 338 | if ((*input).value != val) { 339 | float diff = (*input).value - val; 340 | diff = abs(diff); 341 | 342 | float maxVal; 343 | if ((*input).pin == AV1 || (*input).pin == AV2 || (*input).pin == AV3 || (*input).pin == AV4) { 344 | maxVal = IONO_AV_MAX; 345 | } else if ((*input).pin == AI1 || (*input).pin == AI2 || (*input).pin == AI3 || (*input).pin == AI4) { 346 | maxVal = IONO_AI_MAX; 347 | } else if ((*input).pin == AO1) { 348 | maxVal = IONO_AO_MAX; 349 | } else { 350 | maxVal = -1; 351 | } 352 | 353 | if (diff >= (*input).minVariation || val == 0 || val == maxVal) { 354 | if ((ts - (*input).lastTS) >= (*input).stableTime) { 355 | (*input).value = val; 356 | (*input).lastTS = ts; 357 | if ((*input).callback != NULL) { 358 | (*input).callback((*input).pin, val); 359 | } 360 | if ((*input).linkedPin >= 0) { 361 | switch ((*input).linkMode) { 362 | case LINK_FOLLOW: 363 | write((*input).linkedPin, val); 364 | break; 365 | case LINK_INVERT: 366 | write((*input).linkedPin, val == HIGH ? LOW : HIGH); 367 | break; 368 | case LINK_FLIP_T: 369 | flip((*input).linkedPin); 370 | break; 371 | case LINK_FLIP_H: 372 | if (val == HIGH) { 373 | flip((*input).linkedPin); 374 | } 375 | break; 376 | case LINK_FLIP_L: 377 | if (val == LOW) { 378 | flip((*input).linkedPin); 379 | } 380 | break; 381 | } 382 | } 383 | } 384 | } else { 385 | (*input).lastTS = ts; 386 | } 387 | } else { 388 | (*input).lastTS = ts; 389 | } 390 | } 391 | } 392 | 393 | float IonoClass::read(uint8_t pin) { 394 | if (pin >= DO1 && pin <= DO6) { 395 | return digitalRead(_pinMap[pin]); 396 | } 397 | 398 | if (pin == DI1 || pin == DI2 || pin == DI3 || pin == DI4 || pin == DI5 || pin == DI6) { 399 | return digitalRead(_pinMap[pin]); 400 | } 401 | 402 | if (pin == AV1 || pin == AV2 || pin == AV3 || pin == AV4) { 403 | return analogRead(_pinMap[pin]) * IONO_AV_MAX / ANALOG_READ_MAX; 404 | } 405 | 406 | if (pin == AI1 || pin == AI2 || pin == AI3 || pin == AI4) { 407 | return analogRead(_pinMap[pin]) * IONO_AI_MAX / ANALOG_READ_MAX; 408 | } 409 | 410 | if (pin == AO1) { 411 | return _ao1_val; 412 | } 413 | 414 | return -1; 415 | } 416 | 417 | float IonoClass::readAnalogAvg(uint8_t pin, int n) { 418 | int nn = n; 419 | unsigned long sum = 0; 420 | 421 | if (pin == AV1 || pin == AV2 || pin == AV3 || pin == AV4) { 422 | pin = _pinMap[pin]; 423 | for (; nn > 0; nn--) { 424 | sum += analogRead(pin); 425 | } 426 | return sum / n * IONO_AV_MAX / ANALOG_READ_MAX; 427 | } 428 | 429 | if (pin == AI1 || pin == AI2 || pin == AI3 || pin == AI4) { 430 | pin = _pinMap[pin]; 431 | for (; nn > 0; nn--) { 432 | sum += analogRead(pin); 433 | } 434 | return sum / n * IONO_AI_MAX / ANALOG_READ_MAX; 435 | } 436 | 437 | return -1; 438 | } 439 | 440 | void IonoClass::write(uint8_t pin, float value) { 441 | if ((pin >= DO1 && pin <= DO6) || pin == DI5 || pin == DI6) { 442 | digitalWrite(_pinMap[pin], (int) value); 443 | } 444 | 445 | else if (pin == AO1) { 446 | if (value < 0) { 447 | value = 0; 448 | } else if (value > IONO_AO_MAX) { 449 | value = IONO_AO_MAX; 450 | } 451 | analogWrite(_pinMap[pin], value * ANALOG_WRITE_MAX / IONO_AO_MAX); 452 | _ao1_val = value; 453 | } 454 | } 455 | 456 | void IonoClass::flip(uint8_t pin) { 457 | write(pin, read(pin) == HIGH ? LOW : HIGH); 458 | } 459 | 460 | void IonoClass::serialTxEn(bool enabled) { 461 | #ifdef PIN_TXEN 462 | digitalWrite(PIN_TXEN, enabled ? HIGH : LOW); 463 | #endif 464 | #ifdef PIN_TXEN_N 465 | digitalWrite(PIN_TXEN_N, enabled ? LOW : HIGH); 466 | #endif 467 | } 468 | 469 | IonoClass Iono; 470 | -------------------------------------------------------------------------------- /src/WebServer.h: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-file-style: "k&r"; c-basic-offset: 2; -*- 2 | 3 | Webduino, a simple Arduino web server 4 | Copyright 2009-2014 Ben Combee, Ran Talbott, Christopher Lee, Martin Lormes 5 | Francisco M Cuenca-Acuna 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | 26 | #ifndef WEBDUINO_H_ 27 | #define WEBDUINO_H_ 28 | 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | /******************************************************************** 36 | * CONFIGURATION 37 | ********************************************************************/ 38 | 39 | #define WEBDUINO_VERSION 1007 40 | #define WEBDUINO_VERSION_STRING "1.7" 41 | 42 | // standard END-OF-LINE marker in HTTP 43 | #define CRLF "\r\n" 44 | 45 | // If processConnection is called without a buffer, it allocates one 46 | // of 32 bytes 47 | #define WEBDUINO_DEFAULT_REQUEST_LENGTH 32 48 | 49 | // How long to wait before considering a connection as dead when 50 | // reading the HTTP request. Used to avoid DOS attacks. 51 | #ifndef WEBDUINO_READ_TIMEOUT_IN_MS 52 | #define WEBDUINO_READ_TIMEOUT_IN_MS 1000 53 | #endif 54 | 55 | #ifndef WEBDUINO_COMMANDS_COUNT 56 | #define WEBDUINO_COMMANDS_COUNT 8 57 | #endif 58 | 59 | #ifndef WEBDUINO_URL_PATH_COMMAND_LENGTH 60 | #define WEBDUINO_URL_PATH_COMMAND_LENGTH 8 61 | #endif 62 | 63 | #ifndef WEBDUINO_FAIL_MESSAGE 64 | #define WEBDUINO_FAIL_MESSAGE "error" 65 | #endif 66 | 67 | #ifndef WEBDUINO_AUTH_REALM 68 | #define WEBDUINO_AUTH_REALM "Webduino" 69 | #endif // #ifndef WEBDUINO_AUTH_REALM 70 | 71 | #ifndef WEBDUINO_AUTH_MESSAGE 72 | #define WEBDUINO_AUTH_MESSAGE "

401 Unauthorized

" 73 | #endif // #ifndef WEBDUINO_AUTH_MESSAGE 74 | 75 | #ifndef WEBDUINO_SERVER_ERROR_MESSAGE 76 | #define WEBDUINO_SERVER_ERROR_MESSAGE "

500 Internal Server Error

" 77 | #endif // WEBDUINO_SERVER_ERROR_MESSAGE 78 | 79 | #ifndef WEBDUINO_OUTPUT_BUFFER_SIZE 80 | #define WEBDUINO_OUTPUT_BUFFER_SIZE 32 81 | #endif // WEBDUINO_OUTPUT_BUFFER_SIZE 82 | 83 | #define WEBDUINO_FAVICON_DATA "" 84 | 85 | // add "#define WEBDUINO_SERIAL_DEBUGGING 1" to your application 86 | // before including WebServer.h to have incoming requests logged to 87 | // the serial port. 88 | #ifndef WEBDUINO_SERIAL_DEBUGGING 89 | #define WEBDUINO_SERIAL_DEBUGGING 0 90 | #endif 91 | #if WEBDUINO_SERIAL_DEBUGGING 92 | #include 93 | #endif 94 | 95 | // declared in wiring.h 96 | extern "C" unsigned long millis(void); 97 | 98 | // declare a static string 99 | #ifdef __AVR__ 100 | #define P(name) static const unsigned char name[] __attribute__(( section(".progmem." #name) )) 101 | #else 102 | #define P(name) static const unsigned char name[] 103 | #endif 104 | 105 | // returns the number of elements in the array 106 | #define SIZE(array) (sizeof(array) / sizeof(*array)) 107 | 108 | #ifdef _VARIANT_ARDUINO_DUE_X_ 109 | #define pgm_read_byte(ptr) (unsigned char)(* ptr) 110 | #endif 111 | /******************************************************************** 112 | * DECLARATIONS 113 | ********************************************************************/ 114 | 115 | /* Return codes from nextURLparam. NOTE: URLPARAM_EOS is returned 116 | * when you call nextURLparam AFTER the last parameter is read. The 117 | * last actual parameter gets an "OK" return code. */ 118 | 119 | enum URLPARAM_RESULT { URLPARAM_OK, 120 | URLPARAM_NAME_OFLO, 121 | URLPARAM_VALUE_OFLO, 122 | URLPARAM_BOTH_OFLO, 123 | URLPARAM_EOS // No params left 124 | }; 125 | 126 | class WebServer: public Print 127 | { 128 | public: 129 | // passed to a command to indicate what kind of request was received 130 | enum ConnectionType { INVALID, GET, HEAD, POST, PUT, DELETE, PATCH }; 131 | 132 | // any commands registered with the web server have to follow 133 | // this prototype. 134 | // url_tail contains the part of the URL that wasn't matched against 135 | // the registered command table. 136 | // tail_complete is true if the complete URL fit in url_tail, false if 137 | // part of it was lost because the buffer was too small. 138 | typedef void Command(WebServer &server, ConnectionType type, 139 | char *url_tail, bool tail_complete); 140 | 141 | // Prototype for the optional function which consumes the URL path itself. 142 | // url_path contains pointers to the seperate parts of the URL path where '/' 143 | // was used as the delimiter. 144 | typedef void UrlPathCommand(WebServer &server, ConnectionType type, 145 | char **url_path, char *url_tail, 146 | bool tail_complete); 147 | 148 | // constructor for webserver object 149 | WebServer(const char *urlPrefix = "", uint16_t port = 80); 150 | 151 | // start listening for connections 152 | void begin(); 153 | 154 | // check for an incoming connection, and if it exists, process it 155 | // by reading its request and calling the appropriate command 156 | // handler. This version is for compatibility with apps written for 157 | // version 1.1, and allocates the URL "tail" buffer internally. 158 | void processConnection(); 159 | 160 | // check for an incoming connection, and if it exists, process it 161 | // by reading its request and calling the appropriate command 162 | // handler. This version saves the "tail" of the URL in buff. 163 | void processConnection(char *buff, int *bufflen); 164 | 165 | // set command that's run when you access the root of the server 166 | void setDefaultCommand(Command *cmd); 167 | 168 | // set command run for undefined pages 169 | void setFailureCommand(Command *cmd); 170 | 171 | // add a new command to be run at the URL specified by verb 172 | void addCommand(const char *verb, Command *cmd); 173 | 174 | // Set command that's run if default command or URL specified commands do 175 | // not run, uses extra url_path parameter to allow resolving the URL in the 176 | // function. 177 | void setUrlPathCommand(UrlPathCommand *cmd); 178 | 179 | // utility function to output CRLF pair 180 | void printCRLF(); 181 | 182 | // output a string stored in program memory, usually one defined 183 | // with the P macro 184 | void printP(const unsigned char *str); 185 | 186 | // inline overload for printP to handle signed char strings 187 | void printP(const char *str) { printP((unsigned char*)str); } 188 | 189 | // support for C style formating 190 | void printf(char *fmt, ... ); 191 | #ifdef F 192 | void printf(const __FlashStringHelper *format, ... ); 193 | #endif 194 | 195 | // output raw data stored in program memory 196 | void writeP(const unsigned char *data, size_t length); 197 | 198 | // output HTML for a radio button 199 | void radioButton(const char *name, const char *val, 200 | const char *label, bool selected); 201 | 202 | // output HTML for a checkbox 203 | void checkBox(const char *name, const char *val, 204 | const char *label, bool selected); 205 | 206 | // returns next character or -1 if we're at end-of-stream 207 | int read(); 208 | 209 | // put a character that's been read back into the input pool 210 | void push(int ch); 211 | 212 | // returns true if the string is next in the stream. Doesn't 213 | // consume any character if false, so can be used to try out 214 | // different expected values. 215 | bool expect(const char *expectedStr); 216 | 217 | // returns true if a number, with possible whitespace in front, was 218 | // read from the server stream. number will be set with the new 219 | // value or 0 if nothing was read. 220 | bool readInt(int &number); 221 | 222 | // reads a header value, stripped of possible whitespace in front, 223 | // from the server stream 224 | void readHeader(char *value, int valueLen); 225 | 226 | // Read the next keyword parameter from the socket. Assumes that other 227 | // code has already skipped over the headers, and the next thing to 228 | // be read will be the start of a keyword. 229 | // 230 | // returns true if we're not at end-of-stream 231 | bool readPOSTparam(char *name, int nameLen, char *value, int valueLen); 232 | 233 | // Read the next keyword parameter from the buffer filled by getRequest. 234 | // 235 | // returns 0 if everything weent okay, non-zero if not 236 | // (see the typedef for codes) 237 | URLPARAM_RESULT nextURLparam(char **tail, char *name, int nameLen, 238 | char *value, int valueLen); 239 | 240 | // compare string against credentials in current request 241 | // 242 | // authCredentials must be Base64 encoded outside of Webduino 243 | // (I wanted to be easy on the resources) 244 | // 245 | // returns true if strings match, false otherwise 246 | bool checkCredentials(const char authCredentials[45]); 247 | 248 | // output headers and a message indicating a server error 249 | void httpFail(); 250 | 251 | // output headers and a message indicating "401 Unauthorized" 252 | void httpUnauthorized(); 253 | 254 | // output headers and a message indicating "500 Internal Server Error" 255 | void httpServerError(); 256 | 257 | // output headers indicating "204 No Content" and no further message 258 | void httpNoContent(); 259 | 260 | // output standard headers indicating "200 Success". You can change the 261 | // type of the data you're outputting or also add extra headers like 262 | // "Refresh: 1". Extra headers should each be terminated with CRLF. 263 | void httpSuccess(const char *contentType = "text/html; charset=utf-8", 264 | const char *extraHeaders = NULL); 265 | 266 | // used with POST to output a redirect to another URL. This is 267 | // preferable to outputting HTML from a post because you can then 268 | // refresh the page without getting a "resubmit form" dialog. 269 | void httpSeeOther(const char *otherURL); 270 | 271 | // implementation of write used to implement Print interface 272 | virtual size_t write(uint8_t); 273 | virtual size_t write(const uint8_t *buffer, size_t size); 274 | 275 | // tells if there is anything to process 276 | uint8_t available(); 277 | 278 | // Flush the send buffer 279 | void flushBuf(); 280 | 281 | // Close the current connection and flush ethernet buffers 282 | void reset(); 283 | private: 284 | EthernetServer m_server; 285 | EthernetClient m_client; 286 | const char *m_urlPrefix; 287 | 288 | unsigned char m_pushback[32]; 289 | unsigned char m_pushbackDepth; 290 | 291 | int m_contentLength; 292 | char m_authCredentials[51]; 293 | bool m_readingContent; 294 | 295 | Command *m_failureCmd; 296 | Command *m_defaultCmd; 297 | struct CommandMap 298 | { 299 | const char *verb; 300 | Command *cmd; 301 | } m_commands[WEBDUINO_COMMANDS_COUNT]; 302 | unsigned char m_cmdCount; 303 | UrlPathCommand *m_urlPathCmd; 304 | 305 | uint8_t m_buffer[WEBDUINO_OUTPUT_BUFFER_SIZE]; 306 | uint8_t m_bufFill; 307 | 308 | void getRequest(WebServer::ConnectionType &type, char *request, int *length); 309 | bool dispatchCommand(ConnectionType requestType, char *verb, 310 | bool tail_complete); 311 | void processHeaders(); 312 | void outputCheckboxOrRadio(const char *element, const char *name, 313 | const char *val, const char *label, 314 | bool selected); 315 | 316 | static void defaultFailCmd(WebServer &server, ConnectionType type, 317 | char *url_tail, bool tail_complete); 318 | void noRobots(ConnectionType type); 319 | void favicon(ConnectionType type); 320 | }; 321 | 322 | #endif // WEBDUINO_H_ 323 | -------------------------------------------------------------------------------- /src/IonoWeb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | IonoWeb.cpp - Arduino library for the control of Iono Uno Ethernet via HTTP API 3 | 4 | Copyright (C) 2014-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include "IonoWeb.h" 17 | 18 | WebServer IonoWebClass::_webServer; 19 | 20 | char *IonoWebClass::_host = (char*) malloc(32); 21 | int IonoWebClass::_port = 0; 22 | char *IonoWebClass::_command = (char*) malloc(32); 23 | unsigned long IonoWebClass::_lastSubscribeTime = 0; 24 | 25 | void IonoWebClass::begin(int port) { 26 | _webServer = WebServer("", port); 27 | _webServer.addCommand("api/state", &IonoWebClass::jsonStateCommand); 28 | _webServer.addCommand("api/set", &IonoWebClass::setCommand); 29 | _webServer.addCommand("api/subscribe", &IonoWebClass::subscribeCommand); 30 | _webServer.begin(); 31 | } 32 | 33 | void IonoWebClass::processRequest() { 34 | int len = 128; 35 | char buff[len]; 36 | _webServer.processConnection(buff, &len); 37 | } 38 | 39 | WebServer& IonoWebClass::getWebServer() { 40 | return _webServer; 41 | } 42 | 43 | void IonoWebClass::jsonStateCommand(WebServer &webServer, WebServer::ConnectionType type, char* urlTail, bool tailComplete) { 44 | webServer.httpSuccess("application/json"); 45 | webServer.print("{"); 46 | 47 | webServer.print("\"DO1\":"); 48 | webServer.print(Iono.read(DO1) == HIGH ? 1 : 0); 49 | webServer.print(","); 50 | 51 | webServer.print("\"DO2\":"); 52 | webServer.print(Iono.read(DO2) == HIGH ? 1 : 0); 53 | webServer.print(","); 54 | 55 | webServer.print("\"DO3\":"); 56 | webServer.print(Iono.read(DO3) == HIGH ? 1 : 0); 57 | webServer.print(","); 58 | 59 | webServer.print("\"DO4\":"); 60 | webServer.print(Iono.read(DO4) == HIGH ? 1 : 0); 61 | webServer.print(","); 62 | 63 | webServer.print("\"DO5\":"); 64 | webServer.print(Iono.read(DO5) == HIGH ? 1 : 0); 65 | webServer.print(","); 66 | 67 | webServer.print("\"DO6\":"); 68 | webServer.print(Iono.read(DO6) == HIGH ? 1 : 0); 69 | webServer.print(","); 70 | 71 | webServer.print("\"I1\":{"); 72 | webServer.print("\"D\":"); 73 | webServer.print(Iono.read(DI1) == HIGH ? 1 : 0); 74 | webServer.print(","); 75 | 76 | webServer.print("\"V\":"); 77 | webServer.print(Iono.read(AV1)); 78 | webServer.print(","); 79 | 80 | webServer.print("\"I\":"); 81 | webServer.print(Iono.read(AI1)); 82 | webServer.print("},"); 83 | 84 | webServer.print("\"I2\":{"); 85 | webServer.print("\"D\":"); 86 | webServer.print(Iono.read(DI2) == HIGH ? 1 : 0); 87 | webServer.print(","); 88 | 89 | webServer.print("\"V\":"); 90 | webServer.print(Iono.read(AV2)); 91 | webServer.print(","); 92 | 93 | webServer.print("\"I\":"); 94 | webServer.print(Iono.read(AI2)); 95 | webServer.print("},"); 96 | 97 | webServer.print("\"I3\":{"); 98 | webServer.print("\"D\":"); 99 | webServer.print(Iono.read(DI3) == HIGH ? 1 : 0); 100 | webServer.print(","); 101 | 102 | webServer.print("\"V\":"); 103 | webServer.print(Iono.read(AV3)); 104 | webServer.print(","); 105 | 106 | webServer.print("\"I\":"); 107 | webServer.print(Iono.read(AI3)); 108 | webServer.print("},"); 109 | 110 | webServer.print("\"I4\":{"); 111 | webServer.print("\"D\":"); 112 | webServer.print(Iono.read(DI4) == HIGH ? 1 : 0); 113 | webServer.print(","); 114 | 115 | webServer.print("\"V\":"); 116 | webServer.print(Iono.read(AV4)); 117 | webServer.print(","); 118 | 119 | webServer.print("\"I\":"); 120 | webServer.print(Iono.read(AI4)); 121 | webServer.print("},"); 122 | 123 | webServer.print("\"I5\":{"); 124 | webServer.print("\"D\":"); 125 | webServer.print(Iono.read(DI5) == HIGH ? 1 : 0); 126 | webServer.print("},"); 127 | 128 | webServer.print("\"I6\":{"); 129 | webServer.print("\"D\":"); 130 | webServer.print(Iono.read(DI6) == HIGH ? 1 : 0); 131 | webServer.print("}"); 132 | 133 | webServer.print("}"); 134 | } 135 | 136 | void IonoWebClass::setCommand(WebServer &webServer, WebServer::ConnectionType type, char* urlTail, bool tailComplete) { 137 | if (!tailComplete) { 138 | webServer.httpFail(); 139 | return; 140 | } 141 | 142 | char name[8]; 143 | char value[8]; 144 | URLPARAM_RESULT rc; 145 | 146 | while (strlen(urlTail)) { 147 | rc = webServer.nextURLparam(&urlTail, name, 8, value, 8); 148 | if (rc == URLPARAM_EOS) { 149 | webServer.httpFail(); 150 | return; 151 | } 152 | 153 | if (strlen(name) == 3) { 154 | if (name[0] == 'D' && name[1] == 'O') { 155 | uint8_t pin; 156 | switch (name[2]) { 157 | case '1': 158 | pin = DO1; 159 | break; 160 | case '2': 161 | pin = DO2; 162 | break; 163 | case '3': 164 | pin = DO3; 165 | break; 166 | case '4': 167 | pin = DO4; 168 | break; 169 | case '5': 170 | pin = DO5; 171 | break; 172 | case '6': 173 | pin = DO6; 174 | break; 175 | default: 176 | webServer.httpFail(); 177 | return; 178 | } 179 | 180 | if (value[0] == 'f') { 181 | Iono.flip(pin); 182 | } else { 183 | Iono.write(pin, value[0] == '1' ? HIGH : LOW); 184 | } 185 | 186 | } else if (name[0] == 'A' && name[1] == 'O' && name[2] == '1') { 187 | Iono.write(AO1, atof(value)); 188 | } 189 | } 190 | } 191 | 192 | webServer.httpSuccess(); 193 | } 194 | 195 | void IonoWebClass::subscribeCommand(WebServer &webServer, WebServer::ConnectionType type, char* urlTail, bool tailComplete) { 196 | if (!tailComplete) { 197 | webServer.httpFail(); 198 | return; 199 | } 200 | 201 | unsigned long stableTime = 0; 202 | float minVariation = 0; 203 | 204 | char host[32]; 205 | int port = 80; 206 | char command[32]; 207 | uint8_t mode1 = 1; 208 | uint8_t mode2 = 1; 209 | uint8_t mode3 = 1; 210 | uint8_t mode4 = 1; 211 | 212 | char name[8]; 213 | char value[32]; 214 | URLPARAM_RESULT rc; 215 | 216 | while (strlen(urlTail)) { 217 | rc = webServer.nextURLparam(&urlTail, name, 8, value, 32); 218 | if (rc == URLPARAM_EOS) { 219 | webServer.httpFail(); 220 | return; 221 | } 222 | 223 | if (strcmp(name, "st") == 0) { 224 | stableTime = atol(value); 225 | 226 | } else if (strcmp(name, "mv") == 0) { 227 | minVariation = atof(value); 228 | 229 | } else if (strcmp(name, "host") == 0) { 230 | strncpy(host, value, 32); 231 | 232 | } else if (strcmp(name, "port") == 0) { 233 | port = atoi(value); 234 | 235 | } else if (strcmp(name, "cmd") == 0) { 236 | strncpy(command, value, 32); 237 | 238 | } else if (strcmp(name, "mode1") == 0) { 239 | switch (value[0]) { 240 | case 'i': 241 | mode1 = 3; 242 | break; 243 | 244 | case 'v': 245 | mode1 = 2; 246 | break; 247 | } 248 | 249 | } else if (strcmp(name, "mode2") == 0) { 250 | switch (value[0]) { 251 | case 'i': 252 | mode2 = 3; 253 | break; 254 | 255 | case 'v': 256 | mode2 = 2; 257 | break; 258 | } 259 | 260 | } else if (strcmp(name, "mode3") == 0) { 261 | switch (value[0]) { 262 | case 'i': 263 | mode3 = 3; 264 | break; 265 | 266 | case 'v': 267 | mode3 = 2; 268 | break; 269 | } 270 | 271 | } else if (strcmp(name, "mode4") == 0) { 272 | switch (value[0]) { 273 | case 'i': 274 | mode4 = 3; 275 | break; 276 | 277 | case 'v': 278 | mode4 = 2; 279 | break; 280 | } 281 | } 282 | } 283 | 284 | subscribe(stableTime, minVariation, host, port, command, mode1, mode2, mode3, mode4); 285 | jsonStateCommand(webServer, type, urlTail, tailComplete); 286 | } 287 | 288 | void IonoWebClass::subscribe(unsigned long stableTime, float minVariation, char *host, int port, char *command, uint8_t mode1, uint8_t mode2, uint8_t mode3, uint8_t mode4) { 289 | strncpy(_host, host, 32); 290 | _port = port; 291 | strncpy(_command, command, 32); 292 | 293 | Iono.subscribeDigital(DO1, stableTime, &callDigitalURL); 294 | Iono.subscribeDigital(DO2, stableTime, &callDigitalURL); 295 | Iono.subscribeDigital(DO3, stableTime, &callDigitalURL); 296 | Iono.subscribeDigital(DO4, stableTime, &callDigitalURL); 297 | Iono.subscribeDigital(DO5, stableTime, &callDigitalURL); 298 | Iono.subscribeDigital(DO6, stableTime, &callDigitalURL); 299 | 300 | switch (mode1) { 301 | case 3: 302 | Iono.subscribeAnalog(AI1, stableTime, minVariation, &callAnalogURL); 303 | break; 304 | 305 | case 2: 306 | Iono.subscribeAnalog(AV1, stableTime, minVariation, &callAnalogURL); 307 | break; 308 | 309 | default: 310 | Iono.subscribeDigital(DI1, stableTime, &callDigitalURL); 311 | } 312 | 313 | switch (mode2) { 314 | case 3: 315 | Iono.subscribeAnalog(AI2, stableTime, minVariation, &callAnalogURL); 316 | break; 317 | 318 | case 2: 319 | Iono.subscribeAnalog(AV2, stableTime, minVariation, &callAnalogURL); 320 | break; 321 | 322 | default: 323 | Iono.subscribeDigital(DI2, stableTime, &callDigitalURL); 324 | } 325 | 326 | switch (mode3) { 327 | case 3: 328 | Iono.subscribeAnalog(AI3, stableTime, minVariation, &callAnalogURL); 329 | break; 330 | 331 | case 2: 332 | Iono.subscribeAnalog(AV3, stableTime, minVariation, &callAnalogURL); 333 | break; 334 | 335 | default: 336 | Iono.subscribeDigital(DI3, stableTime, &callDigitalURL); 337 | } 338 | 339 | switch (mode4) { 340 | case 3: 341 | Iono.subscribeAnalog(AI4, stableTime, minVariation, &callAnalogURL); 342 | break; 343 | 344 | case 2: 345 | Iono.subscribeAnalog(AV4, stableTime, minVariation, &callAnalogURL); 346 | break; 347 | 348 | default: 349 | Iono.subscribeDigital(DI4, stableTime, &callDigitalURL); 350 | } 351 | 352 | Iono.subscribeDigital(DI5, stableTime, &callDigitalURL); 353 | Iono.subscribeDigital(DI6, stableTime, &callDigitalURL); 354 | _lastSubscribeTime = millis(); 355 | } 356 | 357 | void IonoWebClass::callDigitalURL(uint8_t pin, float value) { 358 | const char *v = value == HIGH ? "1" : "0"; 359 | switch (pin) { 360 | case DI1: 361 | callURL("DI1", v); 362 | break; 363 | 364 | case DI2: 365 | callURL("DI2", v); 366 | break; 367 | 368 | case DI3: 369 | callURL("DI3", v); 370 | break; 371 | 372 | case DI4: 373 | callURL("DI4", v); 374 | break; 375 | 376 | case DI5: 377 | callURL("DI5", v); 378 | break; 379 | 380 | case DI6: 381 | callURL("DI6", v); 382 | break; 383 | 384 | case DO1: 385 | callURL("DO1", v); 386 | break; 387 | 388 | case DO2: 389 | callURL("DO2", v); 390 | break; 391 | 392 | case DO3: 393 | callURL("DO3", v); 394 | break; 395 | 396 | case DO4: 397 | callURL("DO4", v); 398 | break; 399 | 400 | case DO5: 401 | callURL("DO5", v); 402 | break; 403 | 404 | case DO6: 405 | callURL("DO6", v); 406 | break; 407 | } 408 | } 409 | 410 | void IonoWebClass::callAnalogURL(uint8_t pin, float value) { 411 | char sVal[6]; 412 | ftoa(sVal, value); 413 | switch (pin) { 414 | case AV1: 415 | callURL("AV1", sVal); 416 | break; 417 | 418 | case AI1: 419 | callURL("AI1", sVal); 420 | break; 421 | 422 | case AV2: 423 | callURL("AV2", sVal); 424 | break; 425 | 426 | case AI2: 427 | callURL("AI2", sVal); 428 | break; 429 | 430 | case AV3: 431 | callURL("AV3", sVal); 432 | break; 433 | 434 | case AI3: 435 | callURL("AI3", sVal); 436 | break; 437 | 438 | case AV4: 439 | callURL("AV4", sVal); 440 | break; 441 | 442 | case AI4: 443 | callURL("AI4", sVal); 444 | break; 445 | } 446 | } 447 | 448 | void IonoWebClass::callURL(const char *pin, const char *value) { 449 | if (_port != 0) { 450 | if (millis() > _lastSubscribeTime + SUBSCRIBE_TIMEOUT) { 451 | _port = 0; 452 | return; 453 | } 454 | 455 | EthernetClient client; 456 | for (uint8_t i = 0; i < 4; i++) { 457 | if (client.connect(_host, _port)) { 458 | client.print("GET "); 459 | client.print(_command); 460 | client.print("?"); 461 | client.print(pin); 462 | client.print("="); 463 | client.print(value); 464 | client.println(" HTTP/1.1"); 465 | client.println("Connection: close"); 466 | client.println(); 467 | break; 468 | } 469 | } 470 | client.stop(); 471 | } 472 | } 473 | 474 | void IonoWebClass::ftoa(char *sVal, float fVal) { 475 | fVal += 0.005; 476 | 477 | int dVal = fVal; 478 | int dec = (int)(fVal * 100) % 100; 479 | 480 | int i = 0; 481 | int d = dVal / 10; 482 | if (d != 0) { 483 | sVal[i++] = d + '0'; 484 | } 485 | sVal[i++] = (dVal % 10) + '0'; 486 | sVal[i++] = '.'; 487 | sVal[i++] = (dec / 10) + '0'; 488 | sVal[i++] = (dec % 10) + '0'; 489 | sVal[i] = '\0'; 490 | } 491 | 492 | IonoWebClass IonoWeb; 493 | -------------------------------------------------------------------------------- /examples/WebApp/iono.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | iono 5 | 178 | 179 | 428 | 429 | 430 | 431 |
432 |
433 |
434 | IONO 435 | 436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 | Saving configuration, please wait... 444 |
445 |
446 |
447 | 448 | 449 |
450 |
451 |
ok
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 | 465 | V 466 |
467 |
set
468 |
469 |
470 |
471 |
472 |
473 | 474 | 475 | 476 | -------------------------------------------------------------------------------- /examples/ModbusTcpServerYun/ModbusTcpServerYun.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusTcpServerYun.ino - A Modbus TCP server for Iono Uno with Arduino YUN 3 | 4 | Copyright (C) 2018-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define DELAY 50 // the debounce delay in milliseconds 22 | #define CLIENTS_MAX 5 23 | #define CLIENT_TIMEOUT 30000 24 | 25 | short values[6]; // current valid state for digital inputs 26 | short lastvalues[6]; // values read in last loop 27 | long times[6]; // last change timestamp 28 | unsigned short counters[6]; // digital inputs counter 29 | 30 | BridgeServer server(502); // Modbus TCP server listening on port 502 31 | 32 | BridgeClient clients[CLIENTS_MAX]; 33 | 34 | int mbapindex[CLIENTS_MAX]; // set to -1 when done reading MBAP header 35 | int pduindex[CLIENTS_MAX]; 36 | int pdulength[CLIENTS_MAX]; 37 | 38 | byte mbap[CLIENTS_MAX][7]; 39 | byte pdu[CLIENTS_MAX][16]; 40 | byte rpdu[CLIENTS_MAX][16]; 41 | 42 | unsigned long lastrequest[CLIENTS_MAX]; 43 | 44 | void setup() { 45 | Bridge.begin(); 46 | 47 | server.noListenOnLocalhost(); 48 | server.begin(); 49 | 50 | Iono.setup(); 51 | 52 | // set initial status of digital inputs to unknown 53 | for (int i = 0; i < 6; i++) { 54 | values[i] = -1; 55 | lastvalues[i] = -1; 56 | times[i] = 0; 57 | } 58 | 59 | for (int i = 0; i < CLIENTS_MAX; i++) { 60 | mbapindex[i] = 0; 61 | pduindex[i] = 0; 62 | } 63 | } 64 | 65 | void loop() { 66 | debounce(); 67 | 68 | BridgeClient client = server.accept(); 69 | if (client) { 70 | int i = 0; 71 | for (i = 0; i < CLIENTS_MAX; i++) { 72 | if (!clients[i]) { 73 | break; 74 | } 75 | } 76 | if (i >= CLIENTS_MAX) { 77 | i = 0; 78 | for (i = 0; i < CLIENTS_MAX; i++) { 79 | if (millis() - lastrequest[i] >= CLIENT_TIMEOUT) { 80 | clients[i].stop(); 81 | break; 82 | } 83 | } 84 | } 85 | if (i < CLIENTS_MAX) { 86 | clients[i] = client; 87 | mbapindex[i] = 0; 88 | pduindex[i] = 0; 89 | lastrequest[i] = millis(); 90 | } else { 91 | client.stop(); 92 | } 93 | } 94 | 95 | for (int i = 0; i < CLIENTS_MAX; i++) { 96 | if (clients[i]) { 97 | while (clients[i].available()) { 98 | byte b = clients[i].read(); 99 | if (b != -1) { 100 | if (!inputProcessor(i, b)) { 101 | clients[i].stop(); 102 | break; 103 | } 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | boolean inputProcessor(int clientIdx, byte b) { 111 | switch (mbapindex[clientIdx]) { 112 | case 0: // transaction identifier 113 | case 1: 114 | mbap[clientIdx][mbapindex[clientIdx]++] = b; 115 | break; 116 | case 2: // protocol identifier must be 0 117 | case 3: 118 | if (b != 0) { 119 | mbapindex[clientIdx] = 0; 120 | pduindex[clientIdx] = 0; 121 | return false; 122 | } 123 | mbap[clientIdx][mbapindex[clientIdx]++] = b; 124 | break; 125 | case 4: // length, MSB 126 | mbap[clientIdx][mbapindex[clientIdx]++] = b; 127 | pdulength[clientIdx] = b << 8; 128 | break; 129 | case 5: // length, LSB 130 | mbap[clientIdx][mbapindex[clientIdx]++] = b; 131 | pdulength[clientIdx] += b - 1; 132 | if (pdulength[clientIdx] > 0 && pdulength[clientIdx] <= sizeof(pdu[clientIdx])) { 133 | } else { 134 | mbapindex[clientIdx] = 0; 135 | pduindex[clientIdx] = 0; 136 | return false; 137 | } 138 | break; 139 | case 6: // unit id 140 | mbap[clientIdx][mbapindex[clientIdx]] = b; 141 | mbapindex[clientIdx] = -1; 142 | break; 143 | case -1: // reading PDU 144 | pdu[clientIdx][pduindex[clientIdx]++] = b; 145 | if (pduindex[clientIdx] == pdulength[clientIdx]) { 146 | processPdu(clientIdx, mbap[clientIdx], pdu[clientIdx], rpdu[clientIdx]); 147 | mbapindex[clientIdx] = 0; 148 | pduindex[clientIdx] = 0; 149 | } 150 | break; 151 | default: 152 | mbapindex[clientIdx]++; 153 | break; 154 | } 155 | return true; 156 | } 157 | 158 | void processPdu(int clientIdx, byte *mbap, byte *pdu, byte *rpdu) { 159 | unsigned int start, quantity; 160 | mbap[4] = 0; 161 | switch (pdu[0]) { 162 | case 1: // read coils 163 | // read status of output relays (DO1-DO6), Modbus address 1-6 164 | if (pdu[1] == 0 && pdu[3] == 0 && pdu[2] > 0 && pdu[4] > 0 && pdu[2] + pdu[4] <= 7) { 165 | mbap[5] = 4; 166 | rpdu[0] = rpdu[1] = 1; 167 | rpdu[2] = 0; 168 | for (int i = pdu[2] + pdu[4] - 1; i >= pdu[2]; i--) { 169 | rpdu[2] <<= 1; 170 | if (Iono.read(indexToDigitalOutput(i)) == HIGH) { 171 | rpdu[2] += 1; 172 | } 173 | } 174 | } else { 175 | mbap[5] = 3; 176 | rpdu[0] = 0x81; 177 | rpdu[1] = 2; // illegal data address 178 | } 179 | break; 180 | case 2: // read discrete inputs 181 | // read status of digital inputs (DI1-DI6), Modbus address 101-106 (with de-bouce) and 111-116 (no de-bouce) 182 | if (pdu[1] == 0 && pdu[3] == 0 && (pdu[2] > 100 && pdu[2] < 107 || pdu[2] > 110 && pdu[2] < 117) && pdu[4] > 0 && pdu[4] < 7) { 183 | mbap[5] = 4; 184 | rpdu[0] = 2; 185 | rpdu[1] = 1; 186 | rpdu[2] = 0; 187 | for (int i = pdu[4]; i > 0 ; i--) { 188 | rpdu[2] <<= 1; 189 | if (pdu[2] > 110) { // no de-bouce 190 | if (Iono.read(indexToDigitalInput(i + pdu[2] - 111)) == HIGH) { 191 | rpdu[2] += 1; 192 | } 193 | } else { // de-bounce 194 | if (values[i + pdu[2] - 102] == 1) { 195 | rpdu[2] += 1; 196 | } 197 | } 198 | } 199 | } else { 200 | mbap[5] = 3; 201 | rpdu[0] = 0x82; 202 | rpdu[1] = 2; // illegal data address 203 | } 204 | break; 205 | case 3: // read holding registers 206 | // read status of analog output (AO1), Modbus address 601 207 | if (pdu[1] == 2 && pdu[2] == 89 && pdu[3] == 0 && pdu[4] == 1) { 208 | int v = Iono.read(AO1) * 1000; 209 | mbap[5] = 5; 210 | rpdu[0] = 3; 211 | rpdu[1] = 2; 212 | rpdu[2] = (byte)(v >> 8); 213 | rpdu[3] = (byte)(v & 0xff); 214 | } else { 215 | mbap[5] = 3; 216 | rpdu[0] = 0x83; 217 | rpdu[1] = 2; // illegal data address 218 | } 219 | break; 220 | case 4: // read input registers 221 | start = (pdu[1] << 8) + pdu[2]; 222 | quantity = (pdu[3] << 8) + pdu[4]; 223 | if (start >= 201 && start <= 204 && start + quantity <= 205) { 224 | // read status of analog voltage inputs (AV1-AV4), Modbus address 201-204 225 | mbap[5] = 2 * quantity + 3; 226 | rpdu[0] = 4; 227 | rpdu[1] = 2 * quantity; 228 | int v; 229 | for (int i = 1; i <= quantity; i++) { 230 | v = Iono.read(indexToVoltageInput(i + start - 201)) * 1000; 231 | rpdu[i * 2] = (byte)(v >> 8); 232 | rpdu[1 + i * 2] = (byte)(v & 0xff); 233 | } 234 | } else if (start >= 301 && start <= 304 && start + quantity <= 305) { 235 | // read status of analog current inputs (AI1-AI4), Modbus address 301-304 236 | mbap[5] = 2 * quantity + 3; 237 | rpdu[0] = 4; 238 | rpdu[1] = 2 * quantity; 239 | int v; 240 | for (int i = 1; i <= quantity; i++) { 241 | v = Iono.read(indexToCurrentInput(i + start - 301)) * 1000; 242 | rpdu[i * 2] = (byte)(v >> 8); 243 | rpdu[1 + i * 2] = (byte)(v & 0xff); 244 | } 245 | } else if (start >= 1001 && start <= 1006 && start + quantity <= 1007) { 246 | // read status of digital inputs (DI1-DI6) counters, Modbus address 1001-1006 247 | mbap[5] = 2 * quantity + 3; 248 | rpdu[0] = 4; 249 | rpdu[1] = 2 * quantity; 250 | int v; 251 | for (int i = 1; i <= quantity; i++) { 252 | v = counters[i + start - 1002]; 253 | rpdu[i * 2] = (byte)(v >> 8); 254 | rpdu[1 + i * 2] = (byte)(v & 0xff); 255 | } 256 | } else if (start == 64990 && quantity == 4) { 257 | // read identifier code 258 | mbap[5] = 8 + 3; 259 | rpdu[0] = 4; 260 | rpdu[1] = 8; 261 | rpdu[2] = 0xCA; // fixed 262 | rpdu[3] = 0xFE; // fixed 263 | rpdu[4] = 0xBE; // fixed 264 | rpdu[5] = 0xAF; // fixed 265 | rpdu[6] = 0x15; // Iono Uno (0x10) + Arduino YUN (0x05) 266 | rpdu[7] = 0x02; // App ID: Modbus TCP 267 | rpdu[8] = 0x01; // Version High 268 | rpdu[9] = 0x00; // Version Low 269 | } else { 270 | mbap[5] = 3; 271 | rpdu[0] = 0x84; 272 | rpdu[1] = 2; // illegal data address 273 | } 274 | break; 275 | case 5: // write single coil 276 | // command of single output relay (DO1-DO6), Modbus address 1-6 277 | if (pdu[1] == 0 && pdu[2] > 0 && pdu[2] <= 6) { 278 | if (pdu[3] == 0 && pdu[4] == 0) { 279 | Iono.write(indexToDigitalOutput(pdu[2]), LOW); 280 | mbap[5] = 6; 281 | for (int i = 0; i < 5; i++) { 282 | rpdu[i] = pdu[i]; 283 | } 284 | } else if (pdu[3] == 0xff && pdu[4] == 0) { 285 | Iono.write(indexToDigitalOutput(pdu[2]), HIGH); 286 | mbap[5] = 6; 287 | for (int i = 0; i < 5; i++) { 288 | rpdu[i] = pdu[i]; 289 | } 290 | } else { 291 | mbap[5] = 3; 292 | rpdu[0] = 0x81; 293 | rpdu[1] = 3; // illegal data value 294 | } 295 | } else { 296 | mbap[5] = 3; 297 | rpdu[0] = 0x81; 298 | rpdu[1] = 2; // illegal data address 299 | } 300 | break; 301 | case 6: // write single register 302 | // command of analog output (AO1), Modbus address 601, 0-10000 mV 303 | if (pdu[1] == 2 && pdu[2] == 89) { 304 | int v = (pdu[3] << 8) + pdu[4]; 305 | if (v >= 0 && v <= 10000) { 306 | Iono.write(AO1, v / 1000.0); 307 | mbap[5] = 6; 308 | for (int i = 0; i < 5; i++) { 309 | rpdu[i] = pdu[i]; 310 | } 311 | } else { 312 | mbap[5] = 3; 313 | rpdu[0] = 0x86; 314 | rpdu[1] = 3; // illegal data value 315 | } 316 | } else { 317 | mbap[5] = 3; 318 | rpdu[0] = 0x86; 319 | rpdu[1] = 2; // illegal data address 320 | } 321 | break; 322 | case 15: // write multiple coils 323 | // command of multiple output relays (DO1-DO6), Modbus address 1-6 324 | if (pdu[1] == 0 && pdu[2] > 0 && pdu[3] == 0 && pdu[4] > 0 && pdu[2] + pdu[4] <= 7) { 325 | if (pdu[5] == 1) { 326 | for (int i = pdu[2]; i < pdu[2] + pdu[4]; i++) { 327 | Iono.write(indexToDigitalOutput(i), ((pdu[6] & 1) == 0) ? LOW : HIGH); 328 | pdu[6] >>= 1; 329 | } 330 | mbap[5] = 6; 331 | for (int i = 0; i < 5; i++) { 332 | rpdu[i] = pdu[i]; 333 | } 334 | } else { 335 | mbap[5] = 3; 336 | rpdu[0] = 0x8f; 337 | rpdu[1] = 3; // illegal data value 338 | } 339 | } else { 340 | mbap[5] = 3; 341 | rpdu[0] = 0x8f; 342 | rpdu[1] = 2; // illegal data address 343 | } 344 | break; 345 | default: // error 346 | mbap[5] = 3; 347 | rpdu[0] = pdu[0] | 0x80; 348 | rpdu[1] = 1; // illegal function 349 | break; 350 | } 351 | clients[clientIdx].write(mbap, 7); 352 | clients[clientIdx].write(rpdu, mbap[5] - 1); 353 | clients[clientIdx].flush(); 354 | lastrequest[clientIdx] = millis(); 355 | } 356 | 357 | void debounce() { 358 | int value; 359 | for (int i = 0; i < 6; i++) { 360 | value = (Iono.read(indexToDigitalInput(i + 1)) == HIGH) ? 1 : 0; 361 | if (value != lastvalues[i]) { 362 | times[i] = millis(); 363 | } 364 | if ((millis() - times[i]) > DELAY) { 365 | if (values[i] == -1) { 366 | values[i] = value; 367 | } else if (values[i] != value) { 368 | values[i] = value; 369 | if (value == 1) { 370 | counters[i] = (counters[i] < 65535) ? counters[i] + 1 : 0; 371 | } 372 | } 373 | } 374 | lastvalues[i] = value; 375 | } 376 | } 377 | 378 | int indexToCurrentInput(int i) { 379 | switch (i) { 380 | case 1: 381 | return AI1; 382 | case 2: 383 | return AI2; 384 | case 3: 385 | return AI3; 386 | case 4: 387 | return AI4; 388 | default: 389 | return -1; 390 | } 391 | } 392 | 393 | int indexToVoltageInput(int i) { 394 | switch (i) { 395 | case 1: 396 | return AV1; 397 | case 2: 398 | return AV2; 399 | case 3: 400 | return AV3; 401 | case 4: 402 | return AV4; 403 | default: 404 | return -1; 405 | } 406 | } 407 | 408 | int indexToDigitalInput(int i) { 409 | switch (i) { 410 | case 1: 411 | return DI1; 412 | case 2: 413 | return DI2; 414 | case 3: 415 | return DI3; 416 | case 4: 417 | return DI4; 418 | case 5: 419 | return DI5; 420 | case 6: 421 | return DI6; 422 | default: 423 | return -1; 424 | } 425 | } 426 | 427 | int indexToDigitalOutput(int i) { 428 | switch (i) { 429 | case 1: 430 | return DO1; 431 | case 2: 432 | return DO2; 433 | case 3: 434 | return DO3; 435 | case 4: 436 | return DO4; 437 | case 5: 438 | return DO5; 439 | case 6: 440 | return DO6; 441 | default: 442 | return -1; 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /examples/IonoMkrMQTT/IonoMkrMQTT.ino: -------------------------------------------------------------------------------- 1 | /* 2 | IonoMkrMQTT.cpp 3 | 4 | Copyright (C) 2020-2023 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any nlater version. 13 | See file LICENSE.txt for further informations on licesing terms. 14 | */ 15 | 16 | #include 17 | #include "Watchdog.h" 18 | #include "SerialConfig.h" 19 | 20 | #include 21 | #include 22 | 23 | #define DEBOUNCE_MS 25 24 | #define VALIN 0 25 | #define VALCOUNT 1 26 | #define VALOUT 2 27 | #define VALAO1 3 28 | #define DISCONNECTED 6 29 | 30 | WiFiClient wifiClient; 31 | MqttClient mqttClient(wifiClient); 32 | 33 | uint8_t in1; 34 | uint8_t in2; 35 | uint8_t in3; 36 | uint8_t in4; 37 | float lastSentIn[] = {-1, -1, -1, -1, -1, -1}; 38 | uint16_t lastSentCount[] = {(uint16_t) -1, (uint16_t) -1, (uint16_t) -1, (uint16_t) -1, (uint16_t) -1, (uint16_t) -1}; 39 | uint16_t valCount[] = {0, 0, 0, 0, 0, 0}; 40 | uint8_t lastSentOut[] = {(uint8_t) -1, (uint8_t) -1, (uint8_t) -1, (uint8_t) -1}; 41 | float lastSentAO1 = -1; 42 | unsigned long previousMillis = 0; 43 | String baseTopic = ""; 44 | const int suffTopicValLength = 7; 45 | const int suffTopicCountLength = 9; 46 | char labelsIn[6][sizeof(baseTopic)+suffTopicValLength]; 47 | char labelsOut[4][sizeof(baseTopic)+suffTopicValLength]; 48 | char labelsCount[6][sizeof(baseTopic)+suffTopicCountLength]; 49 | unsigned long lastUpdateSendTs; 50 | unsigned long lastFullStateSendTs; 51 | bool needToSend = false; 52 | bool duplicate = false; 53 | bool retain; 54 | uint8_t qos; 55 | String topicAO1; 56 | bool watchdog; 57 | 58 | void setup() { 59 | pinMode(LED_BUILTIN, OUTPUT); 60 | digitalWrite(LED_BUILTIN, HIGH); 61 | SerialConfig.setup(); 62 | while (!SerialConfig.isConfigured || SerialConfig.isAvailable) { 63 | SerialConfig.process(); 64 | } 65 | 66 | baseTopic = SerialConfig.rootTopic[0] == '\0' ? "/" : SerialConfig.rootTopic; 67 | topicAO1 = String(baseTopic + "ao1" + "/val" + '\0'); 68 | 69 | watchdog = SerialConfig.watchdog == 'T' ? true : false; 70 | 71 | initialize(); 72 | 73 | lastUpdateSendTs = lastFullStateSendTs = millis(); 74 | digitalWrite(LED_BUILTIN, LOW); 75 | 76 | // set the message receive callback 77 | mqttClient.onMessage(messageReceived); 78 | 79 | mqttClient.setId(SerialConfig.clientId); 80 | mqttClient.setUsernamePassword(SerialConfig.username, SerialConfig.password); 81 | mqttClient.setKeepAliveInterval(atoi(SerialConfig.keepAlive) * 1000); 82 | retain = SerialConfig.retain == 'T' ? true : false; 83 | switch (SerialConfig.qos) { 84 | case '0': 85 | qos = 0; 86 | break; 87 | case '1': 88 | qos = 1; 89 | break; 90 | case '2': 91 | qos = 2; 92 | } 93 | 94 | setWillMessage(); 95 | 96 | Serial.println("Hello!"); 97 | } 98 | 99 | // set a will message, used by the broker when the connection dies unexpectantly 100 | void setWillMessage() { 101 | String willPayload = SerialConfig.willPayload; 102 | String willTopic = SerialConfig.willTopic; 103 | if (willTopic.length() > 0 && willPayload.length() > 0) { 104 | mqttClient.beginWill(String(baseTopic + willTopic + '\0'), willPayload.length(), retain, qos); 105 | mqttClient.print(willPayload); 106 | mqttClient.endWill(); 107 | } 108 | } 109 | 110 | void connectToWifi() { 111 | static unsigned long firstConnAttempt = millis(); 112 | if (WiFi.begin(SerialConfig.ssid, SerialConfig.netpass) != WL_CONNECTED) { 113 | Serial.print("."); 114 | if (millis() - firstConnAttempt > 60000) { 115 | WiFi.end(); 116 | Serial.print(" "); 117 | firstConnAttempt = millis(); 118 | } 119 | } else { 120 | Serial.print("\nIP Address: "); 121 | Serial.println(WiFi.localIP()); 122 | } 123 | } 124 | 125 | void connectToBroker() { 126 | static unsigned long firstConnAttempt = millis(); 127 | Serial.println("Connecting to MQTT broker..."); 128 | mqttClient.setConnectionTimeout(5000); 129 | if (!mqttClient.connect(SerialConfig.brokerAddr, atoi(SerialConfig.numPort))) { 130 | Serial.print("MQTT connection failed! Error code = "); 131 | Serial.println(mqttClient.connectError()); 132 | if (millis() - firstConnAttempt > 60000) { 133 | WiFi.end(); 134 | firstConnAttempt = millis(); 135 | } 136 | } else { 137 | // the MQTT broker doesn't memorize session info about this board, 138 | // so every time there's a reconnection all subscriptions must be re-done 139 | Serial.println("Subscribing..."); 140 | subscribeToAll(); 141 | Serial.println("Ready"); 142 | } 143 | } 144 | 145 | void subscribeToAll() { 146 | mqttClient.subscribe(labelsOut[0], qos); 147 | mqttClient.subscribe(labelsOut[1], qos); 148 | mqttClient.subscribe(labelsOut[2], qos); 149 | mqttClient.subscribe(labelsOut[3], qos); 150 | mqttClient.subscribe(topicAO1, qos); 151 | } 152 | 153 | void loop() { 154 | Iono.process(); 155 | 156 | if (watchdog) { 157 | Watchdog.clear(); 158 | } 159 | 160 | if (WiFi.status() != WL_CONNECTED) { 161 | connectToWifi(); 162 | 163 | } else if (!mqttClient.connected()) { 164 | connectToBroker(); 165 | 166 | } else { 167 | unsigned long now = millis(); 168 | 169 | if (now - previousMillis >= 100) { 170 | // poll() calls ping() every keepaliveinterval (default 60 secs) to allow the library to send MQTT keep alives. 171 | // It also calls messageReceived if there are bytes to read. poll() takes about 1 millisec 172 | mqttClient.poll(); 173 | previousMillis = now; 174 | } 175 | 176 | // periodically resend all input statuses 177 | if (now - lastFullStateSendTs >= 15 * 60000 || now - lastUpdateSendTs >= 7 * 60000) { 178 | sendState(true); 179 | lastFullStateSendTs = now; 180 | } else if (needToSend) { 181 | sendState(false); 182 | } 183 | } 184 | 185 | if (SerialConfig.isAvailable) { 186 | SerialConfig.process(); 187 | } 188 | } 189 | 190 | void send(int opt, int val, int i) { 191 | if (opt == VALIN) { 192 | mqttClient.beginMessage(labelsIn[i], retain, qos, duplicate); 193 | } 194 | else if (opt == VALCOUNT) { 195 | mqttClient.beginMessage(labelsCount[i], retain, qos, duplicate); 196 | } 197 | else if (opt == VALOUT) { 198 | mqttClient.beginMessage(labelsOut[i], retain, qos, duplicate); 199 | } 200 | else { 201 | mqttClient.beginMessage(topicAO1, retain, qos, duplicate); 202 | } 203 | mqttClient.print(val); 204 | mqttClient.endMessage(); 205 | } 206 | 207 | bool sendState(bool sendAll) { 208 | float valIn[6]; 209 | uint8_t valOut[4]; 210 | float valAO1; 211 | bool sent = false; 212 | int inputToSend; 213 | char mode; 214 | 215 | digitalWrite(LED_BUILTIN, HIGH); 216 | 217 | valIn[0] = Iono.read(in1); 218 | valIn[1] = Iono.read(in2); 219 | valIn[2] = Iono.read(in3); 220 | valIn[3] = Iono.read(in4); 221 | valIn[4] = Iono.read(DI5); 222 | valIn[5] = Iono.read(DI6); 223 | valOut[0] = (uint8_t) Iono.read(DO1); 224 | valOut[1] = (uint8_t) Iono.read(DO2); 225 | valOut[2] = (uint8_t) Iono.read(DO3); 226 | valOut[3] = (uint8_t) Iono.read(DO4); 227 | valAO1 = Iono.read(AO1); 228 | 229 | // send input statuses 230 | for (int i = 0; i < 6; i++) { 231 | if ((SerialConfig.modes[i] != '-') && (sendAll || lastSentIn[i] != valIn[i])) { 232 | mode = SerialConfig.modes[i]; 233 | // if input is voltage convert into millivolt, if input is amperage convert into microampere 234 | inputToSend = (mode == 'V' || mode == 'I') ? valIn[i] * 1000 : valIn[i]; 235 | send(VALIN, inputToSend, i); 236 | sent = true; 237 | } 238 | } 239 | 240 | // send digital input counters 241 | for (int i = 0; i < 6; i++) { 242 | if (SerialConfig.modes[i] == 'D') { 243 | if (sendAll || lastSentCount[i] != valCount[i]) { 244 | send(VALCOUNT, valCount[i], i); 245 | sent = true; 246 | } 247 | } 248 | } 249 | 250 | // send digital output statuses 251 | for (int i = 0; i < 4; i++) { 252 | if (sendAll || lastSentOut[i] != valOut[i]) { 253 | send(VALOUT, valOut[i], i); 254 | sent = true; 255 | } 256 | } 257 | 258 | // send analog output status 259 | if (sendAll || lastSentAO1 != valAO1) { 260 | send(VALAO1, (int) (valAO1 * 1000), 0); 261 | sent = true; 262 | } 263 | 264 | for (int i = 0; i < 6; i++) { 265 | lastSentIn[i] = valIn[i]; 266 | } 267 | for (int i = 0; i < 6; i++) { 268 | lastSentCount[i] = valCount[i]; 269 | } 270 | for (int i = 0; i < 4; i++) { 271 | lastSentOut[i] = valOut[i]; 272 | } 273 | lastSentAO1 = valAO1; 274 | 275 | if (sent) { 276 | lastUpdateSendTs = millis(); 277 | } 278 | 279 | needToSend = false; 280 | 281 | digitalWrite(LED_BUILTIN, LOW); 282 | } 283 | 284 | //mqtt receive callback function 285 | void messageReceived(int messageSize) { 286 | String topic = mqttClient.messageTopic(); 287 | String payload = mqttClient.readString(); 288 | 289 | if (topic.equals(labelsOut[0])) { 290 | Iono.write(DO1, payload.equals("0") ? LOW : HIGH); 291 | lastSentOut[0] = -1; 292 | } 293 | else if (topic.equals(labelsOut[1])) { 294 | Iono.write(DO2, payload.equals("0") ? LOW : HIGH); 295 | lastSentOut[1] = -1; 296 | } 297 | else if (topic.equals(labelsOut[2])) { 298 | Iono.write(DO3, payload.equals("0") ? LOW : HIGH); 299 | lastSentOut[2] = -1; 300 | } 301 | else if (topic.equals(labelsOut[3])) { 302 | Iono.write(DO4, payload.equals("0") ? LOW : HIGH); 303 | lastSentOut[3] = -1; 304 | } 305 | else if (topic.equals(topicAO1)) { 306 | // convert voltage from millivolt to volt 307 | Iono.write(AO1, payload.toDouble() / 1000.0); 308 | lastSentAO1 = -1; 309 | } 310 | } 311 | 312 | // a value has changed and need to be sent, increment digital input counters 313 | void inputsCallback(uint8_t pin, float value) { 314 | int idx; 315 | if (value == HIGH) { 316 | switch (pin) { 317 | case DI1: idx = 0; break; 318 | case DI2: idx = 1; break; 319 | case DI3: idx = 2; break; 320 | case DI4: idx = 3; break; 321 | case DI5: idx = 4; break; 322 | case DI6: idx = 5; break; 323 | default: idx = -1; break; 324 | } 325 | if (idx >= 0) { 326 | // increment digital input counters 327 | valCount[idx]++; 328 | } 329 | } 330 | 331 | needToSend = true; 332 | } 333 | 334 | void initialize() { 335 | if (watchdog) { 336 | Watchdog.setup(); 337 | } 338 | 339 | Iono.setup(); 340 | 341 | Iono.subscribeDigital(DO1, 0, &inputsCallback); 342 | Iono.subscribeDigital(DO2, 0, &inputsCallback); 343 | Iono.subscribeDigital(DO3, 0, &inputsCallback); 344 | Iono.subscribeDigital(DO4, 0, &inputsCallback); 345 | Iono.subscribeAnalog(AO1, 0, 0, &inputsCallback); 346 | 347 | subscribeMultimode(SerialConfig.modes[0], &in1, DI1, AV1, AI1); 348 | subscribeMultimode(SerialConfig.modes[1], &in2, DI2, AV2, AI2); 349 | subscribeMultimode(SerialConfig.modes[2], &in3, DI3, AV3, AI3); 350 | subscribeMultimode(SerialConfig.modes[3], &in4, DI4, AV4, AI4); 351 | subscribeMultimode(SerialConfig.modes[4], NULL, DI5, 0, 0); 352 | subscribeMultimode(SerialConfig.modes[5], NULL, DI6, 0, 0); 353 | 354 | if (SerialConfig.rules[0] != '\0') { 355 | setLink(SerialConfig.modes[0], SerialConfig.rules[0], DI1, DO1); 356 | setLink(SerialConfig.modes[1], SerialConfig.rules[1], DI2, DO2); 357 | setLink(SerialConfig.modes[2], SerialConfig.rules[2], DI3, DO3); 358 | setLink(SerialConfig.modes[3], SerialConfig.rules[3], DI4, DO4); 359 | } 360 | 361 | String label; 362 | for (int i = 0; i < 6; i++) { 363 | switch (SerialConfig.modes[i]) { 364 | case 'D': 365 | label = String(baseTopic + "di" + (i+1) + "/val" + '\0'); 366 | label.toCharArray(labelsIn[i], label.length()); 367 | label = String(baseTopic + "di" + (i+1) + "/count" + '\0'); 368 | label.toCharArray(labelsCount[i], label.length()); 369 | break; 370 | case 'V': 371 | label = String(baseTopic + "av" + (i+1) + "/val" + '\0'); 372 | label.toCharArray(labelsIn[i], label.length()); 373 | break; 374 | case 'I': 375 | label = String(baseTopic + "ai" + (i+1) + "/val" + '\0'); 376 | label.toCharArray(labelsIn[i], label.length()); 377 | break; 378 | default: 379 | break; 380 | } 381 | } 382 | 383 | for (int i = 0; i < 4; i++) { 384 | label = String(baseTopic + "do" + (i+1) + "/val" + '\0'); 385 | label.toCharArray(labelsOut[i], label.length()); 386 | } 387 | } 388 | 389 | // predispose inputs respecting serial configuration 390 | void subscribeMultimode(char mode, uint8_t* inx, uint8_t dix, uint8_t avx, uint8_t aix) { 391 | switch (mode) { 392 | case 'D': 393 | if (inx != NULL) { 394 | *inx = dix; 395 | } 396 | Iono.subscribeDigital(dix, DEBOUNCE_MS, &inputsCallback); 397 | break; 398 | case 'V': 399 | if (inx != NULL) { 400 | *inx = avx; 401 | } 402 | Iono.subscribeAnalog(avx, DEBOUNCE_MS, 0.1, &inputsCallback); 403 | break; 404 | case 'I': 405 | if (inx != NULL) { 406 | *inx = aix; 407 | } 408 | Iono.subscribeAnalog(aix, DEBOUNCE_MS, 0.1, &inputsCallback); 409 | break; 410 | default: 411 | break; 412 | } 413 | } 414 | 415 | void setLink(char mode, char rule, uint8_t dix, uint8_t dox) { 416 | if (mode == 'V' || mode == 'I') { 417 | return; 418 | } 419 | switch (rule) { 420 | case 'F': 421 | Iono.linkDiDo(dix, dox, LINK_FOLLOW, DEBOUNCE_MS); 422 | break; 423 | case 'I': 424 | Iono.linkDiDo(dix, dox, LINK_INVERT, DEBOUNCE_MS); 425 | break; 426 | case 'T': 427 | Iono.linkDiDo(dix, dox, LINK_FLIP_T, DEBOUNCE_MS); 428 | break; 429 | case 'H': 430 | Iono.linkDiDo(dix, dox, LINK_FLIP_H, DEBOUNCE_MS); 431 | break; 432 | case 'L': 433 | Iono.linkDiDo(dix, dox, LINK_FLIP_L, DEBOUNCE_MS); 434 | break; 435 | default: 436 | break; 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /examples/IonoMkrLoRaWAN/SerialConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | SerialConfig.h 3 | 4 | Copyright (C) 2018-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #ifndef SerialConfig_h 17 | #define SerialConfig_h 18 | 19 | #include 20 | #include 21 | #include "Watchdog.h" 22 | 23 | #define CONSOLE_TIMEOUT 20000 24 | #define _PORT_USB SERIAL_PORT_MONITOR 25 | #define _PORT_RS485 SERIAL_PORT_HARDWARE 26 | 27 | class SerialConfig { 28 | private: 29 | static Stream *_port; 30 | static short _spacesCounter; 31 | static char _inBuffer[64]; 32 | static int _fCntMemAddr; 33 | 34 | static void _close(); 35 | static void _enterConsole(); 36 | // static void _enterConfigWizard(); 37 | static void _exportConfig(); 38 | static bool _importConfig(); 39 | static bool _consumeWhites(); 40 | template 41 | static void _print(T text); 42 | static void _readEchoLine(int maxLen, bool returnOnMaxLen, 43 | bool upperCase, int (*charFilter)(int, int, int, int), int p1, int p2); 44 | static int _betweenFilter(int c, int idx, int min, int max); 45 | static int _orFilter(int c, int idx, int p1, int p2); 46 | static int _modesFilter(int c, int idx, int p1, int p2); 47 | static int _rulesFilter(int c, int idx, int p1, int p2); 48 | static void _printConfiguration(char* devAddr, char* nwkSKey, char* appSKey, 49 | _lora_band band, uint8_t dataRate, char *modes, char *rules); 50 | static void _confirmConfiguration(char* devAddr, char* nwkSKey, char* appSKey, 51 | _lora_band band, uint8_t dataRate, char *modes, char *rules); 52 | static bool _readEepromConfig(); 53 | static bool _writeEepromConfig(char* devAddr, char* nwkSKey, char* appSKey, 54 | _lora_band band, uint8_t dataRate, char *modes, char *rules); 55 | 56 | public: 57 | static bool isConfigured; 58 | static bool isAvailable; 59 | 60 | static uint32_t fCntUp; 61 | static uint32_t fCntDown; 62 | 63 | static char devAddr[9]; 64 | static char nwkSKey[33]; 65 | static char appSKey[33]; 66 | static _lora_band band; 67 | static uint8_t dataRate; 68 | static char modes[7]; 69 | static char rules[5]; 70 | 71 | static void setup(); 72 | static void process(); 73 | static void writeFCntUp(uint32_t); 74 | static void writeFCntDown(uint32_t); 75 | }; 76 | 77 | bool SerialConfig::isConfigured = false; 78 | bool SerialConfig::isAvailable = true; 79 | 80 | uint32_t SerialConfig::fCntUp = 0; 81 | uint32_t SerialConfig::fCntDown = 0; 82 | 83 | Stream *SerialConfig::_port = NULL; 84 | short SerialConfig::_spacesCounter = 0; 85 | char SerialConfig::_inBuffer[64]; 86 | int SerialConfig::_fCntMemAddr; 87 | 88 | char SerialConfig::devAddr[9]; 89 | char SerialConfig::nwkSKey[33]; 90 | char SerialConfig::appSKey[33]; 91 | _lora_band SerialConfig::band; 92 | uint8_t SerialConfig::dataRate; 93 | char SerialConfig::modes[7]; 94 | char SerialConfig::rules[5]; 95 | 96 | void SerialConfig::setup() { 97 | _PORT_USB.begin(9600); 98 | _PORT_RS485.begin(9600); 99 | 100 | isConfigured = _readEepromConfig(); 101 | 102 | if (!isConfigured) { 103 | for (int i = 0; i < 32; i++) { 104 | if (i < 8) { 105 | devAddr[i] = '0'; 106 | } 107 | nwkSKey[i] = '0'; 108 | appSKey[i] = '0'; 109 | } 110 | devAddr[8] = '\0'; 111 | nwkSKey[32] = '\0'; 112 | appSKey[32] = '\0'; 113 | band = EU868; 114 | dataRate = 5; 115 | strncpy(modes, "DDDDDD", 6); 116 | modes[6] = '\0'; 117 | strncpy(rules, "----", 4); 118 | rules[4] = '\0'; 119 | } 120 | } 121 | 122 | void SerialConfig::process() { 123 | if (_port == NULL) { 124 | if (_PORT_USB.available()) { 125 | _port = &_PORT_USB; 126 | } else if (_PORT_RS485.available()) { 127 | _port = &_PORT_RS485; 128 | } 129 | } 130 | 131 | while (_port != NULL && _port->available()) { 132 | int b = _port->read(); 133 | if (b == ' ') { 134 | if (_spacesCounter >= 4) { 135 | _enterConsole(); 136 | } else { 137 | _spacesCounter++; 138 | } 139 | } else if (isConfigured) { 140 | _close(); 141 | } else { 142 | _port = NULL; 143 | } 144 | } 145 | 146 | if (isConfigured && millis() > CONSOLE_TIMEOUT) { 147 | _close(); 148 | } 149 | } 150 | 151 | void SerialConfig::_close() { 152 | isAvailable = false; 153 | _PORT_USB.end(); 154 | _PORT_RS485.end(); 155 | } 156 | 157 | void SerialConfig::_enterConsole() { 158 | Watchdog.disable(); 159 | delay(100); 160 | while(_port->read() >= 0) { 161 | delay(5); 162 | } 163 | while (true) { 164 | _print("=== Sfera Labs - Iono MKR LoRaWAN configuration - v1.0.0 ===\r\n" 165 | /* "\r\n 1. Configuration wizard" */ 166 | "\r\n 1. Import configuration" 167 | "\r\n 2. Export configuration" 168 | "\r\n\r\n> " 169 | ); 170 | _readEchoLine(1, false, false, &_betweenFilter, '1', '2'); 171 | switch (_inBuffer[0]) { 172 | /* 173 | case '1': 174 | _enterConfigWizard(); 175 | break; 176 | */ 177 | case '1': 178 | if (!_importConfig()) { 179 | while(_port->read() >= 0) { 180 | delay(5); 181 | } 182 | _print("\r\nError\r\n\r\n"); 183 | } 184 | break; 185 | case '2': 186 | _exportConfig(); 187 | break; 188 | default: 189 | break; 190 | } 191 | } 192 | } 193 | 194 | bool SerialConfig::_importConfig() { 195 | char devAddrNew[9]; 196 | char nwkSKeyNew[33]; 197 | char appSKeyNew[33]; 198 | _lora_band bandNew = EU868; 199 | uint8_t dataRateNew = 5; 200 | char modesNew[7]; 201 | char rulesNew[5]; 202 | 203 | String l; 204 | int n; 205 | 206 | devAddrNew[0] = '\0'; 207 | nwkSKeyNew[0] = '\0'; 208 | appSKeyNew[0] = '\0'; 209 | modesNew[0] = '\0'; 210 | rulesNew[0] = '\0'; 211 | 212 | _print("\r\nPaste the configuration:\r\n"); 213 | if (!_consumeWhites()) { 214 | return false; 215 | } 216 | _port->setTimeout(300); 217 | while (true) { 218 | l = _port->readStringUntil(':'); 219 | 220 | if (l.endsWith("devAddr")) { 221 | if (!_consumeWhites()) { 222 | return false; 223 | } 224 | n = _port->readBytes(devAddrNew, 8); 225 | if (n != 8) { 226 | return false; 227 | } 228 | devAddrNew[8] = '\0'; 229 | 230 | } else if (l.endsWith("nwkSKey")) { 231 | if (!_consumeWhites()) { 232 | return false; 233 | } 234 | n = _port->readBytes(nwkSKeyNew, 32); 235 | if (n != 32) { 236 | return false; 237 | } 238 | nwkSKeyNew[32] = '\0'; 239 | 240 | } else if (l.endsWith("appSKey")) { 241 | if (!_consumeWhites()) { 242 | return false; 243 | } 244 | n = _port->readBytes(appSKeyNew, 32); 245 | if (n != 32) { 246 | return false; 247 | } 248 | appSKeyNew[32] = '\0'; 249 | 250 | } else if (l.endsWith("rate")) { 251 | dataRateNew = _port->parseInt(); 252 | 253 | } else if (l.endsWith("band")) { 254 | if (!_consumeWhites()) { 255 | return false; 256 | } 257 | n = _port->readBytes(_inBuffer, 2); 258 | if (n != 2) { 259 | return false; 260 | } 261 | if (_inBuffer[0] == 'A' && _inBuffer[1] == 'S') { 262 | bandNew = AS923; 263 | } else if (_inBuffer[0] == 'A' && _inBuffer[1] == 'U') { 264 | bandNew = AU915; 265 | } else if (_inBuffer[0] == 'E' && _inBuffer[1] == 'U') { 266 | bandNew = EU868; 267 | } else if (_inBuffer[0] == 'K' && _inBuffer[1] == 'R') { 268 | bandNew = KR920; 269 | } else if (_inBuffer[0] == 'I' && _inBuffer[1] == 'N') { 270 | bandNew = IN865; 271 | } else if (_inBuffer[0] == 'U' && _inBuffer[1] == 'S') { 272 | bandNew = US915; 273 | } else { 274 | return false; 275 | } 276 | 277 | } else if (l.endsWith("modes")) { 278 | if (!_consumeWhites()) { 279 | return false; 280 | } 281 | n = _port->readBytes(modesNew, 6); 282 | if (n != 6) { 283 | return false; 284 | } 285 | modesNew[6] = '\0'; 286 | 287 | } else if (l.endsWith("rules")) { 288 | if (!_consumeWhites()) { 289 | return false; 290 | } 291 | n = _port->readBytes(rulesNew, 4); 292 | if (n != 4) { 293 | return false; 294 | } 295 | rulesNew[4] = '\0'; 296 | 297 | } else { 298 | break; 299 | } 300 | } 301 | 302 | if (devAddrNew[0] == '\0' || nwkSKeyNew[0] == '\0' || appSKeyNew[0] == '\0' || 303 | modesNew[0] == '\0' || rulesNew[0] == '\0') { 304 | return false; 305 | } 306 | 307 | _confirmConfiguration(devAddrNew, nwkSKeyNew, appSKeyNew, 308 | bandNew, dataRateNew, modesNew, rulesNew); 309 | } 310 | 311 | bool SerialConfig::_consumeWhites() { 312 | int c; 313 | while (true) { 314 | c = _port->peek(); 315 | if (c >= 0) { 316 | if (c == '\b' || c == 127 || c == 27) { 317 | return false; 318 | } 319 | if (c != ' ' && c != '\n' && c != '\r' && c != '\t') { 320 | break; 321 | } 322 | _port->read(); 323 | } 324 | } 325 | return true; 326 | } 327 | 328 | void SerialConfig::_exportConfig() { 329 | if (!isConfigured) { 330 | _print("\r\n*** Not configured ***\r\n\r\nExample:"); 331 | } 332 | _print("\r\n"); 333 | _printConfiguration(devAddr, nwkSKey, appSKey, 334 | band, dataRate, modes, rules); 335 | _print("\r\n"); 336 | } 337 | 338 | template 339 | void SerialConfig::_print(T text) { 340 | digitalWrite(PIN_TXEN, HIGH); 341 | _port->print(text); 342 | _port->flush(); 343 | delay(5); 344 | digitalWrite(PIN_TXEN, LOW); 345 | } 346 | 347 | int SerialConfig::_orFilter(int c, int idx, int p1, int p2) { 348 | if (c == p1 || c == p2) { 349 | return c; 350 | } 351 | return -1; 352 | } 353 | 354 | int SerialConfig::_betweenFilter(int c, int idx, int min, int max) { 355 | if (c >= min && c <= max) { 356 | return c; 357 | } 358 | return -1; 359 | } 360 | 361 | int SerialConfig::_modesFilter(int c, int idx, int p1, int p2) { 362 | if (c == 'D' || c == '-' || (idx < 4 && (c == 'V' || c == 'I'))) { 363 | return c; 364 | } 365 | return -1; 366 | } 367 | 368 | int SerialConfig::_rulesFilter(int c, int idx, int p1, int p2) { 369 | if (c == 'F' || c == 'I' || c == 'H' || c == 'L' || c == 'T' || c == '-') { 370 | return c; 371 | } 372 | return -1; 373 | } 374 | 375 | void SerialConfig::_readEchoLine(int maxLen, bool returnOnMaxLen, 376 | bool upperCase, int (*charFilter)(int, int, int, int), int p1, int p2) { 377 | int c, i = 0, p = 0; 378 | bool eol = false; 379 | while (true) { 380 | while (_port->available() && !eol) { 381 | c = _port->read(); 382 | switch (c) { 383 | case '\r': 384 | case '\n': 385 | eol = true; 386 | break; 387 | case '\b': 388 | case 127: 389 | if (i > 0) { 390 | i--; 391 | } 392 | break; 393 | default: 394 | if (i < maxLen) { 395 | if (upperCase && c >= 'a') { 396 | c -= 32; 397 | } 398 | c = charFilter(c, i, p1, p2); 399 | if (c >= 0) { 400 | _inBuffer[i++] = c; 401 | } 402 | } 403 | if (returnOnMaxLen && i >= maxLen) { 404 | eol = true; 405 | } 406 | break; 407 | } 408 | delay(5); 409 | } 410 | 411 | for (; p < i; p++) { 412 | _print(_inBuffer[p]); 413 | } 414 | for (; p > i; p--) { 415 | _print("\b \b"); 416 | } 417 | if (eol) { 418 | _inBuffer[i] = '\0'; 419 | _print("\r\n"); 420 | return; 421 | } 422 | } 423 | } 424 | 425 | bool SerialConfig::_writeEepromConfig(char* devAddr, char* nwkSKey, char* appSKey, 426 | _lora_band band, uint8_t dataRate, char *modes, char *rules) { 427 | byte checksum = 7; 428 | int a = 2; 429 | for (int i = 0; i < 8; i++) { 430 | EEPROM.write(a++, devAddr[i]); 431 | checksum ^= devAddr[i]; 432 | } 433 | for (int i = 0; i < 32; i++) { 434 | EEPROM.write(a++, nwkSKey[i]); 435 | checksum ^= nwkSKey[i]; 436 | } 437 | for (int i = 0; i < 32; i++) { 438 | EEPROM.write(a++, appSKey[i]); 439 | checksum ^= appSKey[i]; 440 | } 441 | EEPROM.write(a++, band); 442 | checksum ^= band; 443 | EEPROM.write(a++, dataRate); 444 | checksum ^= dataRate; 445 | for (int i = 0; i < 6; i++) { 446 | EEPROM.write(a++, modes[i]); 447 | checksum ^= modes[i]; 448 | } 449 | for (int i = 0; i < 4; i++) { 450 | EEPROM.write(a++, rules[i]); 451 | checksum ^= rules[i]; 452 | } 453 | 454 | EEPROM.write(0, a - 2); 455 | checksum ^= a - 2; 456 | EEPROM.write(1, checksum); 457 | 458 | // fCntUp & fCntDown reset 459 | for (int i = 0; i < 8; i++) { 460 | EEPROM.write(a + i, 0); 461 | } 462 | 463 | EEPROM.commit(); 464 | 465 | return true; 466 | } 467 | 468 | void SerialConfig::writeFCntUp(uint32_t fCntUp) { 469 | EEPROM.write(_fCntMemAddr, (byte) (fCntUp >> 24)); 470 | EEPROM.write(_fCntMemAddr + 1, (byte) (fCntUp >> 16)); 471 | EEPROM.write(_fCntMemAddr + 2, (byte) (fCntUp >> 8)); 472 | EEPROM.write(_fCntMemAddr + 3, (byte) fCntUp); 473 | EEPROM.commit(); 474 | } 475 | 476 | void SerialConfig::writeFCntDown(uint32_t fCntDown) { 477 | EEPROM.write(_fCntMemAddr + 4, (byte) (fCntDown >> 24)); 478 | EEPROM.write(_fCntMemAddr + 5, (byte) (fCntDown >> 16)); 479 | EEPROM.write(_fCntMemAddr + 6, (byte) (fCntDown >> 8)); 480 | EEPROM.write(_fCntMemAddr + 7, (byte) fCntDown); 481 | EEPROM.commit(); 482 | } 483 | 484 | bool SerialConfig::_readEepromConfig() { 485 | if (!EEPROM.isValid()) { 486 | return false; 487 | } 488 | 489 | byte checksum = 7; 490 | int len = EEPROM.read(0) & 0xff; 491 | byte mem[len]; 492 | for (int i = 0; i < len; i++) { 493 | mem[i] = EEPROM.read(i + 2); 494 | checksum ^= mem[i]; 495 | } 496 | checksum ^= len; 497 | if ((EEPROM.read(1) != checksum)) { 498 | return false; 499 | } 500 | 501 | int a = 0; 502 | for (int i = 0; i < 8; i++) { 503 | devAddr[i] = mem[a++]; 504 | } 505 | for (int i = 0; i < 32; i++) { 506 | nwkSKey[i] = mem[a++]; 507 | } 508 | for (int i = 0; i < 32; i++) { 509 | appSKey[i] = mem[a++]; 510 | } 511 | band = (_lora_band) mem[a++]; 512 | dataRate = mem[a++]; 513 | for (int i = 0; i < 6; i++) { 514 | modes[i] = mem[a++]; 515 | } 516 | for (int i = 0; i < 4; i++) { 517 | rules[i] = mem[a++]; 518 | } 519 | 520 | _fCntMemAddr = a + 2; 521 | fCntUp = ((EEPROM.read(_fCntMemAddr) & 0xfful) << 24) + ((EEPROM.read(_fCntMemAddr + 1) & 0xfful) << 16) + ((EEPROM.read(_fCntMemAddr + 2) & 0xfful) << 8) + (EEPROM.read(_fCntMemAddr + 3) & 0xfful); 522 | fCntDown = ((EEPROM.read(_fCntMemAddr + 4) & 0xfful) << 24) + ((EEPROM.read(_fCntMemAddr + 5) & 0xfful) << 16) + ((EEPROM.read(_fCntMemAddr + 6) & 0xfful) << 8) + (EEPROM.read(_fCntMemAddr + 7) & 0xfful); 523 | 524 | return true; 525 | } 526 | 527 | void SerialConfig::_confirmConfiguration(char* devAddr, char* nwkSKey, char* appSKey, 528 | _lora_band band, uint8_t dataRate, char *modes, char *rules) { 529 | 530 | _print("\r\nNew configuration:\r\n"); 531 | 532 | _printConfiguration(devAddr, nwkSKey, appSKey, 533 | band, dataRate, modes, rules); 534 | 535 | _print("\r\nConfirm? (Y/N):\r\n\r\n"); 536 | do { 537 | _print("> "); 538 | _readEchoLine(1, false, true, &_orFilter, 'Y', 'N'); 539 | if (_inBuffer[0] == 'Y') { 540 | _print("\r\nSaving..."); 541 | _writeEepromConfig(devAddr, nwkSKey, appSKey, band, dataRate, modes, rules); 542 | if (_readEepromConfig()) { 543 | _print("\r\nSaved!\r\nResetting... bye!\r\n\r\n"); 544 | delay(1000); 545 | NVIC_SystemReset(); 546 | } else { 547 | _print("\r\nError\r\n\r\n"); 548 | } 549 | break; 550 | } else if (_inBuffer[0] == 'N') { 551 | break; 552 | } 553 | } while (true); 554 | } 555 | 556 | void SerialConfig::_printConfiguration(char* devAddr, char* nwkSKey, char* appSKey, 557 | _lora_band band, uint8_t dataRate, char *modes, char *rules) { 558 | _print("\r\ndevAddr: "); 559 | _print(devAddr); 560 | _print("\r\nnwkSKey: "); 561 | _print(nwkSKey); 562 | _print("\r\nappSKey: "); 563 | _print(appSKey); 564 | _print("\r\nband: "); 565 | switch(band) { 566 | case AS923: _print("AS"); break; 567 | case AU915: _print("AU"); break; 568 | case EU868: _print("EU"); break; 569 | case KR920: _print("KR"); break; 570 | case IN865: _print("IN"); break; 571 | case US915: _print("US"); break; 572 | } 573 | _print("\r\ndata rate: "); 574 | _print(dataRate); 575 | _print("\r\ninput modes: "); 576 | _print(modes); 577 | _print("\r\nI/O rules: "); 578 | _print(rules); 579 | 580 | _print("\r\n"); 581 | } 582 | 583 | extern SerialConfig SerialConfig; 584 | 585 | #endif 586 | -------------------------------------------------------------------------------- /src/IonoModbusRtuSlave.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | IonoModbusRtuSlave.cpp - Modbus RTU Slave library for Iono Uno/MKR/RP 3 | 4 | Copyright (C) 2018-2025 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include "IonoModbusRtuSlave.h" 17 | 18 | #ifdef IONO_RP 19 | #define ONE_WIRE_ENABLED 0 20 | #else 21 | #define ONE_WIRE_ENABLED 1 22 | #endif 23 | 24 | #define WIEGAND_ENABLED 1 25 | 26 | #if ONE_WIRE_ENABLED == 1 27 | #include 28 | #include 29 | #endif 30 | 31 | #if WIEGAND_ENABLED == 1 32 | #include 33 | #endif 34 | 35 | #define VERSION 0x0601 // Version High - Version Low 36 | 37 | #define ANALOG_AVG_N 32 38 | 39 | #define ONE_WIRE_REQ_ITVL 10000 40 | #define ONE_WIRE_MAX_SENS 8 41 | 42 | bool IonoModbusRtuSlaveClass::_di1deb; 43 | bool IonoModbusRtuSlaveClass::_di2deb; 44 | bool IonoModbusRtuSlaveClass::_di3deb; 45 | bool IonoModbusRtuSlaveClass::_di4deb; 46 | bool IonoModbusRtuSlaveClass::_di5deb; 47 | bool IonoModbusRtuSlaveClass::_di6deb; 48 | 49 | word IonoModbusRtuSlaveClass::_di1count = 0; 50 | word IonoModbusRtuSlaveClass::_di2count = 0; 51 | word IonoModbusRtuSlaveClass::_di3count = 0; 52 | word IonoModbusRtuSlaveClass::_di4count = 0; 53 | word IonoModbusRtuSlaveClass::_di5count = 0; 54 | word IonoModbusRtuSlaveClass::_di6count = 0; 55 | 56 | IonoClass::Callback *IonoModbusRtuSlaveClass::_di1Callback = NULL; 57 | IonoClass::Callback *IonoModbusRtuSlaveClass::_di2Callback = NULL; 58 | IonoClass::Callback *IonoModbusRtuSlaveClass::_di3Callback = NULL; 59 | IonoClass::Callback *IonoModbusRtuSlaveClass::_di4Callback = NULL; 60 | IonoClass::Callback *IonoModbusRtuSlaveClass::_di5Callback = NULL; 61 | IonoClass::Callback *IonoModbusRtuSlaveClass::_di6Callback = NULL; 62 | 63 | char IonoModbusRtuSlaveClass::_inMode[4] = {0, 0, 0, 0}; 64 | 65 | ModbusRtuSlaveClass::Callback *IonoModbusRtuSlaveClass::_customCallback = NULL; 66 | 67 | #if ONE_WIRE_ENABLED == 1 68 | OneWire oneWireDi5(IONO_PIN_DI5_BYP); 69 | OneWire oneWireDi6(IONO_PIN_DI6_BYP); 70 | DallasTemperature sensorsDi5(&oneWireDi5); 71 | DallasTemperature sensorsDi6(&oneWireDi6); 72 | DeviceAddress sensorsAddressDi5[ONE_WIRE_MAX_SENS]; 73 | DeviceAddress sensorsAddressDi6[ONE_WIRE_MAX_SENS]; 74 | int sensorsCountDi5 = -1; 75 | int sensorsCountDi6 = -1; 76 | unsigned long sensorsReqTsDi5; 77 | unsigned long sensorsReqTsDi6; 78 | #endif 79 | 80 | bool doTempEnabled[DO_IDX_MAX]; 81 | unsigned long doTempStart[DO_IDX_MAX]; 82 | unsigned long doTempTime[DO_IDX_MAX]; 83 | 84 | #if WIEGAND_ENABLED == 1 85 | Wiegand wgnd(IONO_PIN_DI5_BYP, IONO_PIN_DI6_BYP); 86 | uint64_t wgndData; 87 | bool wgndInit = false; 88 | 89 | void wgndOnData0() { 90 | wgnd.onData0(); 91 | } 92 | 93 | void wgndOnData1() { 94 | wgnd.onData1(); 95 | } 96 | #endif 97 | 98 | void IonoModbusRtuSlaveClass::begin(byte unitAddr, unsigned long baud, unsigned long config, unsigned long diDebounceTime) { 99 | Iono.setup(); 100 | 101 | IONO_RS485.begin(baud, config); 102 | ModbusRtuSlave.setCallback(&IonoModbusRtuSlaveClass::onRequest); 103 | 104 | #ifdef PIN_TXEN 105 | ModbusRtuSlave.begin(unitAddr, &IONO_RS485, baud, PIN_TXEN); 106 | #elif defined(PIN_TXEN_N) 107 | ModbusRtuSlave.begin(unitAddr, &IONO_RS485, baud, PIN_TXEN_N, true); 108 | #else 109 | ModbusRtuSlave.begin(unitAddr, &IONO_RS485, baud, 0); 110 | #endif 111 | 112 | if (_inMode[0] == 0 || _inMode[0] == 'D') { 113 | Iono.subscribeDigital(DI1, diDebounceTime, &onDIChange); 114 | } 115 | if (_inMode[1] == 0 || _inMode[1] == 'D') { 116 | Iono.subscribeDigital(DI2, diDebounceTime, &onDIChange); 117 | } 118 | if (_inMode[2] == 0 || _inMode[2] == 'D') { 119 | Iono.subscribeDigital(DI3, diDebounceTime, &onDIChange); 120 | } 121 | if (_inMode[3] == 0 || _inMode[3] == 'D') { 122 | Iono.subscribeDigital(DI4, diDebounceTime, &onDIChange); 123 | } 124 | Iono.subscribeDigital(DI5, diDebounceTime, &onDIChange); 125 | Iono.subscribeDigital(DI6, diDebounceTime, &onDIChange); 126 | } 127 | 128 | void IonoModbusRtuSlaveClass::setInputMode(int idx, char mode) { 129 | if (idx >= 1 && idx <= 4 && 130 | (mode == 0 || mode == 'D' || mode == 'V' || mode == 'I')) { 131 | _inMode[idx - 1] = mode; 132 | } 133 | } 134 | 135 | void IonoModbusRtuSlaveClass::process() { 136 | ModbusRtuSlave.process(); 137 | Iono.process(); 138 | #if ONE_WIRE_ENABLED == 1 139 | if (sensorsCountDi5 > 0 && millis() - sensorsReqTsDi5 > ONE_WIRE_REQ_ITVL) { 140 | sensorsDi5.requestTemperatures(); 141 | sensorsReqTsDi5 = millis(); 142 | } 143 | if (sensorsCountDi6 > 0 && millis() - sensorsReqTsDi6 > ONE_WIRE_REQ_ITVL) { 144 | sensorsDi6.requestTemperatures(); 145 | sensorsReqTsDi6 = millis(); 146 | } 147 | #endif 148 | for (int i = 0; i < DO_IDX_MAX; i++) { 149 | if (doTempEnabled[i] && millis() - doTempStart[i] > doTempTime[i]) { 150 | Iono.write(indexToDO(i + 1), LOW); 151 | doTempEnabled[i] = false; 152 | } 153 | } 154 | } 155 | 156 | void IonoModbusRtuSlaveClass::setCustomHandler(ModbusRtuSlaveClass::Callback *callback) { 157 | _customCallback = callback; 158 | } 159 | 160 | void IonoModbusRtuSlaveClass::subscribeDigital(uint8_t pin, IonoClass::Callback *callback) { 161 | switch (pin) { 162 | case DI1: 163 | _di1Callback = callback; 164 | break; 165 | 166 | case DI2: 167 | _di2Callback = callback; 168 | break; 169 | 170 | case DI3: 171 | _di3Callback = callback; 172 | break; 173 | 174 | case DI4: 175 | _di4Callback = callback; 176 | break; 177 | 178 | case DI5: 179 | _di5Callback = callback; 180 | break; 181 | 182 | case DI6: 183 | _di6Callback = callback; 184 | break; 185 | } 186 | } 187 | 188 | void IonoModbusRtuSlaveClass::onDIChange(uint8_t pin, float value) { 189 | switch (pin) { 190 | case DI1: 191 | _di1deb = value == HIGH; 192 | if (_di1deb) { 193 | _di1count++; 194 | } 195 | if (_di1Callback != NULL) { 196 | _di1Callback(pin, value); 197 | } 198 | break; 199 | 200 | case DI2: 201 | _di2deb = value == HIGH; 202 | if (_di2deb) { 203 | _di2count++; 204 | } 205 | if (_di2Callback != NULL) { 206 | _di2Callback(pin, value); 207 | } 208 | break; 209 | 210 | case DI3: 211 | _di3deb = value == HIGH; 212 | if (_di3deb) { 213 | _di3count++; 214 | } 215 | if (_di3Callback != NULL) { 216 | _di3Callback(pin, value); 217 | } 218 | break; 219 | 220 | case DI4: 221 | _di4deb = value == HIGH; 222 | if (_di4deb) { 223 | _di4count++; 224 | } 225 | if (_di4Callback != NULL) { 226 | _di4Callback(pin, value); 227 | } 228 | break; 229 | 230 | case DI5: 231 | _di5deb = value == HIGH; 232 | if (_di5deb) { 233 | _di5count++; 234 | } 235 | if (_di5Callback != NULL) { 236 | _di5Callback(pin, value); 237 | } 238 | break; 239 | 240 | case DI6: 241 | _di6deb = value == HIGH; 242 | if (_di6deb) { 243 | _di6count++; 244 | } 245 | if (_di6Callback != NULL) { 246 | _di6Callback(pin, value); 247 | } 248 | break; 249 | } 250 | } 251 | 252 | byte IonoModbusRtuSlaveClass::onRequest(byte unitAddr, byte function, word regAddr, word qty, byte *data) { 253 | byte respCode; 254 | if (_customCallback != NULL) { 255 | respCode = _customCallback(unitAddr, function, regAddr, qty, data); 256 | if (respCode != MB_RESP_PASS) { 257 | return respCode; 258 | } 259 | } 260 | 261 | switch (function) { 262 | case MB_FC_READ_COILS: 263 | if (checkAddrRange(regAddr, qty, 1, DO_IDX_MAX)) { 264 | for (word i = regAddr; i < regAddr + qty; i++) { 265 | ModbusRtuSlave.responseAddBit(Iono.read(indexToDO(i)) == HIGH); 266 | } 267 | return MB_RESP_OK; 268 | } 269 | return MB_EX_ILLEGAL_DATA_ADDRESS; 270 | 271 | case MB_FC_READ_DISCRETE_INPUTS: 272 | if (checkAddrRange(regAddr, qty, 101, 106)) { 273 | for (word i = regAddr - 100; i < regAddr - 100 + qty; i++) { 274 | ModbusRtuSlave.responseAddBit(indexToDIdeb(i)); 275 | } 276 | return MB_RESP_OK; 277 | } 278 | if (checkAddrRange(regAddr, qty, 111, 116)) { 279 | for (word i = regAddr - 110; i < regAddr - 110 + qty; i++) { 280 | if (i > 4 || _inMode[i - 1] == 0 || _inMode[i - 1] == 'D') { 281 | ModbusRtuSlave.responseAddBit(Iono.read(indexToDI(i)) == HIGH); 282 | } else { 283 | ModbusRtuSlave.responseAddBit(false); 284 | } 285 | } 286 | return MB_RESP_OK; 287 | } 288 | return MB_EX_ILLEGAL_DATA_ADDRESS; 289 | 290 | case MB_FC_READ_HOLDING_REGISTERS: 291 | if (regAddr == 601 && qty == 1) { 292 | ModbusRtuSlave.responseAddRegister(Iono.read(AO1) * 1000); 293 | return MB_RESP_OK; 294 | } 295 | #if ONE_WIRE_ENABLED == 1 296 | if (regAddr == 5000 && qty == 1) { 297 | sensorsDi5.begin(); 298 | sensorsCountDi5 = sensorsDi5.getDeviceCount(); 299 | if (sensorsCountDi5 > ONE_WIRE_MAX_SENS) { 300 | sensorsCountDi5 = ONE_WIRE_MAX_SENS; 301 | } 302 | for (int i = 0; i < sensorsCountDi5; i++) { 303 | sensorsDi5.getAddress(sensorsAddressDi5[i], i); 304 | } 305 | sensorsDi5.setWaitForConversion(false); 306 | sensorsDi5.requestTemperatures(); 307 | sensorsReqTsDi5 = millis(); 308 | ModbusRtuSlave.responseAddRegister(sensorsCountDi5); 309 | return MB_RESP_OK; 310 | } 311 | if (checkAddrRange(regAddr, qty, 5001, 5064)) { 312 | for (word i = regAddr - 5001; i < regAddr - 5001 + qty; i++) { 313 | int a = i / 8; 314 | int b = i % 8; 315 | if (a < sensorsCountDi5) { 316 | ModbusRtuSlave.responseAddRegister(sensorsAddressDi5[a][b]); 317 | } else { 318 | ModbusRtuSlave.responseAddRegister(0); 319 | } 320 | } 321 | return MB_RESP_OK; 322 | } 323 | if (regAddr == 6000 && qty == 1) { 324 | sensorsDi6.begin(); 325 | sensorsCountDi6 = sensorsDi6.getDeviceCount(); 326 | if (sensorsCountDi6 > ONE_WIRE_MAX_SENS) { 327 | sensorsCountDi6 = ONE_WIRE_MAX_SENS; 328 | } 329 | for (int i = 0; i < sensorsCountDi6; i++) { 330 | sensorsDi6.getAddress(sensorsAddressDi6[i], i); 331 | } 332 | sensorsDi6.setWaitForConversion(false); 333 | sensorsDi6.requestTemperatures(); 334 | sensorsReqTsDi6 = millis(); 335 | ModbusRtuSlave.responseAddRegister(sensorsCountDi6); 336 | return MB_RESP_OK; 337 | } 338 | if (checkAddrRange(regAddr, qty, 6001, 6064)) { 339 | for (word i = regAddr - 6001; i < regAddr - 6001 + qty; i++) { 340 | int a = i / 8; 341 | int b = i % 8; 342 | if (a < sensorsCountDi6) { 343 | ModbusRtuSlave.responseAddRegister(sensorsAddressDi6[a][b]); 344 | } else { 345 | ModbusRtuSlave.responseAddRegister(0); 346 | } 347 | } 348 | return MB_RESP_OK; 349 | } 350 | #endif 351 | return MB_EX_ILLEGAL_DATA_ADDRESS; 352 | 353 | case MB_FC_READ_INPUT_REGISTER: 354 | if (checkAddrRange(regAddr, qty, 201, 204)) { 355 | for (word i = regAddr - 200; i < regAddr - 200 + qty; i++) { 356 | if (_inMode[i - 1] != 'D') { 357 | ModbusRtuSlave.responseAddRegister(Iono.read(indexToAV(i)) * 1000); 358 | } else { 359 | ModbusRtuSlave.responseAddRegister(0); 360 | } 361 | } 362 | return MB_RESP_OK; 363 | } 364 | if (checkAddrRange(regAddr, qty, 211, 214)) { 365 | for (word i = regAddr - 210; i < regAddr - 210 + qty; i++) { 366 | if (_inMode[i - 1] != 'D') { 367 | ModbusRtuSlave.responseAddRegister(Iono.readAnalogAvg(indexToAV(i), ANALOG_AVG_N) * 1000); 368 | } else { 369 | ModbusRtuSlave.responseAddRegister(0); 370 | } 371 | } 372 | return MB_RESP_OK; 373 | } 374 | if (checkAddrRange(regAddr, qty, 301, 304)) { 375 | for (word i = regAddr - 300; i < regAddr - 300 + qty; i++) { 376 | if (_inMode[i - 1] != 'D') { 377 | ModbusRtuSlave.responseAddRegister(Iono.read(indexToAI(i)) * 1000); 378 | } else { 379 | ModbusRtuSlave.responseAddRegister(0); 380 | } 381 | } 382 | return MB_RESP_OK; 383 | } 384 | if (checkAddrRange(regAddr, qty, 311, 314)) { 385 | for (word i = regAddr - 310; i < regAddr - 310 + qty; i++) { 386 | if (_inMode[i - 1] != 'D') { 387 | ModbusRtuSlave.responseAddRegister(Iono.readAnalogAvg(indexToAI(i), ANALOG_AVG_N) * 1000); 388 | } else { 389 | ModbusRtuSlave.responseAddRegister(0); 390 | } 391 | } 392 | return MB_RESP_OK; 393 | } 394 | if (checkAddrRange(regAddr, qty, 1001, 1006)) { 395 | for (word i = regAddr - 1000; i < regAddr - 1000 + qty; i++) { 396 | ModbusRtuSlave.responseAddRegister(indexToDIcount(i)); 397 | } 398 | return MB_RESP_OK; 399 | } 400 | #if ONE_WIRE_ENABLED == 1 401 | if (checkAddrRange(regAddr, qty, 5101, 5108)) { 402 | if (qty <= sensorsCountDi5) { 403 | for (word i = regAddr - 5101; i < regAddr - 5101 + qty; i++) { 404 | ModbusRtuSlave.responseAddRegister((int)(sensorsDi5.getTempC(sensorsAddressDi5[i]) * 100.0)); 405 | } 406 | return MB_RESP_OK; 407 | } 408 | } 409 | if (checkAddrRange(regAddr, qty, 6101, 6108)) { 410 | if (qty <= sensorsCountDi6) { 411 | sensorsDi6.requestTemperatures(); 412 | for (word i = regAddr - 6101; i < regAddr - 6101 + qty; i++) { 413 | ModbusRtuSlave.responseAddRegister((int)(sensorsDi6.getTempC(sensorsAddressDi6[i]) * 100.0)); 414 | } 415 | return MB_RESP_OK; 416 | } 417 | } 418 | #endif 419 | #if WIEGAND_ENABLED == 1 420 | if (regAddr == 8001 && qty == 1) { 421 | if (!wgndInit) { 422 | wgnd.setup(wgndOnData0, wgndOnData1, false, 700, 2700, 10, 150); 423 | wgndInit = true; 424 | } 425 | ModbusRtuSlave.responseAddRegister(wgnd.getData(&wgndData)); 426 | return MB_RESP_OK; 427 | } 428 | if (regAddr == 8002 && qty <= 4) { 429 | for (unsigned int i = 0; i < qty; i++) { 430 | ModbusRtuSlave.responseAddRegister((wgndData >> (i * 16)) & 0xffff); 431 | } 432 | return MB_RESP_OK; 433 | } 434 | if (regAddr == 8010 && qty == 1) { 435 | ModbusRtuSlave.responseAddRegister(wgnd.getNoise()); 436 | return MB_RESP_OK; 437 | } 438 | #endif 439 | if (regAddr == 99 && qty == 1) { 440 | #ifdef IONO_RP 441 | ModbusRtuSlave.responseAddRegister(0x30); 442 | #elif defined(IONO_MKR) 443 | ModbusRtuSlave.responseAddRegister(0x20); 444 | #else 445 | ModbusRtuSlave.responseAddRegister(0x10); 446 | #endif 447 | return MB_RESP_OK; 448 | } 449 | if (regAddr == 64990 && qty == 4) { 450 | ModbusRtuSlave.responseAddRegister(0xCAFE); // fixed 451 | ModbusRtuSlave.responseAddRegister(0xBEAF); // fixed 452 | #ifdef IONO_RP 453 | ModbusRtuSlave.responseAddRegister(0x3001); // Iono RP - App ID: Modbus RTU 454 | #elif defined(IONO_MKR) 455 | ModbusRtuSlave.responseAddRegister(0x2001); // Iono MKR - App ID: Modbus RTU 456 | #else 457 | ModbusRtuSlave.responseAddRegister(0x1001); // Iono Uno - App ID: Modbus RTU 458 | #endif 459 | ModbusRtuSlave.responseAddRegister(VERSION); 460 | return MB_RESP_OK; 461 | } 462 | return MB_EX_ILLEGAL_DATA_ADDRESS; 463 | 464 | case MB_FC_WRITE_SINGLE_COIL: 465 | if (regAddr >= 1 && regAddr <= DO_IDX_MAX) { 466 | bool on = ModbusRtuSlave.getDataCoil(function, data, 0); 467 | Iono.write(indexToDO(regAddr), on ? HIGH : LOW); 468 | return MB_RESP_OK; 469 | } 470 | return MB_EX_ILLEGAL_DATA_ADDRESS; 471 | 472 | case MB_FC_WRITE_MULTIPLE_REGISTERS: 473 | case MB_FC_WRITE_SINGLE_REGISTER: 474 | if (regAddr == 601) { 475 | word value = ModbusRtuSlave.getDataRegister(function, data, 0); 476 | if (value < 0 || value > 10000) { 477 | return MB_EX_ILLEGAL_DATA_VALUE; 478 | } 479 | Iono.write(AO1, value / 1000.0); 480 | return MB_RESP_OK; 481 | } 482 | if (checkAddrRange(regAddr, qty, 11, 10 + DO_IDX_MAX)) { 483 | for (word i = regAddr - 10; i < regAddr - 10 + qty; i++) { 484 | doTempTime[i - 1] = 100 * ModbusRtuSlave.getDataRegister(function, data, i - (regAddr - 10)); 485 | if (doTempTime[i - 1] > 0) { 486 | doTempEnabled[i - 1] = true; 487 | doTempStart[i - 1] = millis(); 488 | Iono.write(indexToDO(i), HIGH); 489 | } 490 | } 491 | return MB_RESP_OK; 492 | } 493 | return MB_EX_ILLEGAL_DATA_ADDRESS; 494 | 495 | case MB_FC_WRITE_MULTIPLE_COILS: 496 | if (checkAddrRange(regAddr, qty, 1, DO_IDX_MAX)) { 497 | for (word i = regAddr; i < regAddr + qty; i++) { 498 | bool on = ModbusRtuSlave.getDataCoil(function, data, i - regAddr); 499 | Iono.write(indexToDO(i), on ? HIGH : LOW); 500 | } 501 | return MB_RESP_OK; 502 | } 503 | return MB_EX_ILLEGAL_DATA_ADDRESS; 504 | 505 | default: 506 | return MB_EX_ILLEGAL_FUNCTION; 507 | } 508 | } 509 | 510 | bool IonoModbusRtuSlaveClass::checkAddrRange(word regAddr, word qty, word min, word max) { 511 | return regAddr >= min && regAddr <= max && regAddr + qty <= max + 1; 512 | } 513 | 514 | uint8_t IonoModbusRtuSlaveClass::indexToDO(int i) { 515 | switch (i) { 516 | case 1: 517 | return DO1; 518 | case 2: 519 | return DO2; 520 | case 3: 521 | return DO3; 522 | case 4: 523 | return DO4; 524 | case 5: 525 | return DO5; 526 | case 6: 527 | return DO6; 528 | default: 529 | return -1; 530 | } 531 | } 532 | 533 | uint8_t IonoModbusRtuSlaveClass::indexToDI(int i) { 534 | switch (i) { 535 | case 1: 536 | return DI1; 537 | case 2: 538 | return DI2; 539 | case 3: 540 | return DI3; 541 | case 4: 542 | return DI4; 543 | case 5: 544 | return DI5; 545 | case 6: 546 | return DI6; 547 | default: 548 | return -1; 549 | } 550 | } 551 | 552 | bool IonoModbusRtuSlaveClass::indexToDIdeb(int i) { 553 | switch (i) { 554 | case 1: 555 | return _di1deb; 556 | case 2: 557 | return _di2deb; 558 | case 3: 559 | return _di3deb; 560 | case 4: 561 | return _di4deb; 562 | case 5: 563 | return _di5deb; 564 | case 6: 565 | return _di6deb; 566 | default: 567 | return false; 568 | } 569 | } 570 | 571 | word IonoModbusRtuSlaveClass::indexToDIcount(int i) { 572 | switch (i) { 573 | case 1: 574 | return _di1count; 575 | case 2: 576 | return _di2count; 577 | case 3: 578 | return _di3count; 579 | case 4: 580 | return _di4count; 581 | case 5: 582 | return _di5count; 583 | case 6: 584 | return _di6count; 585 | default: 586 | return -1; 587 | } 588 | } 589 | 590 | uint8_t IonoModbusRtuSlaveClass::indexToAV(int i) { 591 | switch (i) { 592 | case 1: 593 | return AV1; 594 | case 2: 595 | return AV2; 596 | case 3: 597 | return AV3; 598 | case 4: 599 | return AV4; 600 | default: 601 | return -1; 602 | } 603 | } 604 | 605 | uint8_t IonoModbusRtuSlaveClass::indexToAI(int i) { 606 | switch (i) { 607 | case 1: 608 | return AI1; 609 | case 2: 610 | return AI2; 611 | case 3: 612 | return AI3; 613 | case 4: 614 | return AI4; 615 | default: 616 | return -1; 617 | } 618 | } 619 | -------------------------------------------------------------------------------- /examples/WebApp/WebApp.ino: -------------------------------------------------------------------------------- 1 | /* 2 | WebApp.ino - Web application to control, monitor and configure Iono Uno Ethernet 3 | 4 | Copyright (C) 2014-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include 17 | #include 18 | #ifdef ARDUINO_AVR_LEONARDO_ETH 19 | #include 20 | #else 21 | #include 22 | #endif 23 | #include 24 | #include 25 | 26 | byte mac[6]; 27 | byte ip[4]; 28 | byte dnsa[4]; 29 | byte gateway[4]; 30 | byte subnet[4]; 31 | 32 | char pwd[9]; 33 | 34 | void configCmd(WebServer &webServer, WebServer::ConnectionType type, char* urlTail, bool tailComplete) { 35 | 36 | webServer.httpSuccess(); 37 | webServer.print("{"); 38 | 39 | webServer.print("\"i\":\""); 40 | for (int i = 0; i < 4; i++) { 41 | if (i != 0) { 42 | webServer.print("."); 43 | } 44 | webServer.print(ip[i]); 45 | } 46 | 47 | webServer.print("\",\"d\":\""); 48 | for (int i = 0; i < 4; i++) { 49 | if (i != 0) { 50 | webServer.print("."); 51 | } 52 | webServer.print(dnsa[i]); 53 | } 54 | 55 | webServer.print("\",\"g\":\""); 56 | for (int i = 0; i < 4; i++) { 57 | if (i != 0) { 58 | webServer.print("."); 59 | } 60 | webServer.print(gateway[i]); 61 | } 62 | 63 | webServer.print("\",\"s\":\""); 64 | for (int i = 0; i < 4; i++) { 65 | if (i != 0) { 66 | webServer.print("."); 67 | } 68 | webServer.print(subnet[i]); 69 | } 70 | 71 | webServer.print("\",\"m\":\""); 72 | for (int i = 0; i < 6; i++) { 73 | if (i != 0) { 74 | webServer.print(":"); 75 | } 76 | char hex[3]; 77 | hex[0] = (mac[i] >> 4) + 0x30; 78 | if (hex[0] > 0x39) hex[0] +=7; 79 | hex[1] = (mac[i] & 0x0f) + 0x30; 80 | if (hex[1] > 0x39) hex[1] +=7; 81 | hex[2] = '\0'; 82 | webServer.print(hex); 83 | } 84 | 85 | webServer.print("\",\"p\":\""); 86 | webServer.print(pwd); 87 | 88 | webServer.print("\"}"); 89 | } 90 | 91 | void webPageCmd(WebServer &webServer, WebServer::ConnectionType type, char* urlTail, bool tailComplete) { 92 | if (strlen(urlTail) == 0) { // serve page 93 | webServer.httpSuccess("text/html", "Content-Encoding: gzip\r\n"); 94 | 95 | // These are the bytes of the zipped web page. 96 | // HOW-TO: 97 | // minify iono.html (e.g. use https://kangax.github.io/html-minifier/) 98 | // gzip -n -k -9 iono-min.html 99 | // hexdump -ve '1/1 "0x%.2x,"' iono-min.html.gz > bytes.txt 100 | P(page) = {0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0x03,0x95,0x58,0x7f,0x73,0xe2,0x46,0x12,0xfd,0xff,0x3e,0x85,0x76,0x36,0x87,0xa5,0x42,0x08,0x84,0xc1,0xf6,0x0a,0x04,0x95,0xcb,0xa6,0x12,0x5f,0x25,0xd9,0xd4,0xed,0x56,0xea,0xaa,0xbc,0x4e,0x95,0x7e,0x0c,0x30,0xb1,0x90,0x14,0x69,0x10,0x78,0x31,0xdf,0xfd,0x5e,0xcf,0x48,0x58,0x06,0x67,0x53,0xe7,0xb2,0x61,0xd4,0xd3,0x33,0xd3,0xfd,0xba,0xfb,0x4d,0xcb,0xd3,0x35,0x97,0x81,0x11,0x65,0xa9,0xe4,0xa9,0xf4,0xd9,0x56,0xc4,0x72,0xe5,0xc7,0xbc,0x12,0x11,0xef,0xa9,0x07,0x5b,0xa4,0x42,0x8a,0x20,0xe9,0x95,0x51,0x90,0x70,0xdf,0x65,0x69,0xb0,0xe6,0x7e,0x25,0xf8,0x36,0xcf,0x0a,0x39,0x9b,0x4a,0x21,0x13,0x3e,0x13,0x59,0x9a,0x4d,0xfb,0x7a,0x3c,0x2d,0xe5,0x23,0xbe,0xc2,0x2c,0x7e,0xdc,0x4b,0xbe,0x93,0xbd,0x20,0x11,0xcb,0xd4,0x8b,0x70,0x00,0x2f,0x26,0x0b,0x1c,0xe5,0xb9,0x57,0xf9,0xce,0x58,0xf1,0xa4,0xe2,0x52,0x44,0xc1,0x64,0x1d,0x14,0x4b,0x91,0x7a,0x83,0x49,0x1e,0xc4,0xb1,0x48,0x97,0x18,0x85,0x41,0xf4,0xb0,0x2c,0xb2,0x4d,0x1a,0x7b,0x6f,0x2f,0xaf,0xde,0x1d,0x9c,0x64,0xbf,0x48,0xb2,0x40,0x7a,0x09,0x5f,0xc8,0x83,0x53,0xd4,0x4f,0x85,0x58,0xae,0xf0,0x18,0xec,0xf3,0xac,0x84,0x99,0x59,0xea,0x05,0x61,0x99,0x25,0x1b,0xc9,0x0f,0x22,0xcd,0x37,0x72,0xdf,0xdb,0xf2,0xf0,0x41,0xc0,0x86,0x3c,0xe7,0x41,0x11,0xa4,0x11,0xf7,0xd2,0x2c,0xe5,0x93,0x46,0xae,0x0c,0x2c,0xc5,0x17,0xde,0x0b,0xe2,0x3f,0x36,0xa5,0xd4,0xb3,0x2b,0x4e,0xfb,0x7a,0xc3,0xcb,0x7c,0x77,0x70,0x08,0x9c,0xbd,0xc2,0xc2,0x73,0x07,0x83,0x7f,0x1e,0x1c,0xe5,0x99,0x16,0x0c,0x6f,0x06,0xf9,0xee,0x68,0xbf,0x11,0x6c,0x64,0x76,0x70,0xca,0xad,0xd8,0xd7,0xa2,0x31,0x6d,0x80,0x67,0x67,0xdd,0x92,0x18,0xc3,0x51,0x2d,0x36,0xb4,0x91,0x67,0xc6,0xd7,0x3b,0xf6,0xc8,0x59,0xaf,0xf7,0x0e,0x3f,0x38,0xa5,0x12,0xa5,0x08,0x45,0x22,0xe4,0xa3,0xb7,0x12,0x71,0xcc,0x53,0xbd,0x45,0x12,0x84,0x3c,0xd9,0xc7,0xa2,0xcc,0x93,0xe0,0xd1,0x0b,0x93,0x2c,0x7a,0x98,0x1c,0x37,0x2c,0x78,0x12,0x48,0x51,0xf1,0x49,0xb6,0x91,0x89,0x48,0x79,0x0b,0xe3,0x21,0xb6,0xd4,0x4e,0x5c,0x91,0x0f,0xb5,0xcb,0x97,0x34,0x6e,0x83,0x7f,0x73,0xf3,0x6e,0x12,0x66,0x45,0xcc,0x8b,0x5e,0x11,0xc4,0x62,0x53,0x7a,0x6e,0xe3,0x94,0x3e,0xda,0x0b,0x16,0x88,0xab,0xdd,0x12,0x84,0x7c,0x91,0x15,0xfc,0x15,0xaf,0xea,0x34,0xf3,0x2e,0x2e,0x0e,0xe7,0xea,0x32,0xcb,0x95,0x4d,0xca,0x67,0x1a,0x84,0x99,0x94,0xd9,0x5a,0x0d,0x0b,0x1d,0x8e,0x13,0xd3,0x16,0x8b,0xc5,0xa9,0x69,0xa3,0x57,0x4c,0xdb,0x4b,0xc4,0xbd,0x31,0x25,0x49,0x0c,0x67,0x58,0x4e,0xe8,0xb0,0x51,0x73,0xd8,0xe8,0xf9,0xb0,0xd1,0x11,0x94,0xe1,0xd5,0xc9,0x69,0x61,0x18,0x46,0xd1,0xe8,0x2f,0x0f,0x54,0x91,0xf4,0xa2,0x15,0x8f,0x1e,0x78,0xdc,0x6d,0x1f,0xdf,0x8e,0xe5,0x19,0xbc,0x57,0x83,0xc8,0xbd,0xb9,0x3a,0x84,0x85,0xbd,0x2a,0xf6,0x51,0x82,0x1c,0xf5,0x60,0xca,0xea,0xe0,0xf0,0x02,0xcf,0x59,0x92,0x15,0xda,0xcf,0x3a,0x7b,0x28,0x0f,0x9a,0x50,0x53,0xa6,0xe2,0x6c,0x59,0x1a,0x8e,0x6c,0x54,0xaf,0xaf,0x6f,0x9a,0x00,0xf7,0x6a,0x97,0x74,0xb4,0x48,0x8b,0xd7,0x49,0x7b,0x3d,0x3a,0x8a,0x44,0x2d,0x1a,0x91,0x5d,0x67,0xc5,0xaa,0x95,0x62,0x51,0xb5,0x6a,0xaf,0xc9,0x99,0x9b,0x97,0x0b,0xea,0x42,0x54,0x0c,0x70,0x6e,0x77,0x8f,0xf0,0x56,0xbe,0x53,0xf9,0xab,0x7a,0x43,0xe9,0x90,0x15,0x79,0x53,0x17,0x43,0xcc,0xaa,0xfa,0x31,0x74,0x19,0x1d,0xf3,0xd4,0x3d,0x81,0xac,0x58,0x86,0x81,0x39,0x1c,0x0d,0x6c,0xf5,0x37,0x1e,0xd8,0xce,0x8d,0x75,0x12,0x95,0xf1,0x19,0x4a,0x79,0x5d,0x69,0xcf,0xc7,0xbb,0x3a,0xea,0xb4,0x8c,0x12,0xcb,0x40,0x8a,0x8a,0xd8,0x78,0x25,0xdd,0x15,0x07,0x50,0xed,0x34,0x81,0x6c,0xf2,0x72,0xdc,0x4c,0xd4,0x25,0xd8,0x62,0xa4,0x33,0x64,0x9a,0xf8,0x61,0x33,0x43,0x31,0x00,0x79,0x3b,0xa8,0x97,0xd7,0xa6,0xb5,0x96,0xd7,0x6c,0x33,0x3e,0x1e,0xe1,0x94,0x79,0x70,0x72,0xfe,0xe5,0x90,0x26,0xc3,0x86,0x9a,0xae,0x4f,0x33,0x8b,0xe3,0x67,0x71,0xdd,0x16,0xd1,0x46,0x41,0xd1,0x5b,0x92,0x63,0x88,0xaf,0x49,0x01,0xb2,0xdf,0x82,0x49,0x1a,0xfc,0x54,0xb5,0x1b,0x6e,0x83,0xc6,0x09,0x10,0x04,0x58,0x13,0x94,0x6b,0xed,0xc7,0x24,0xda,0x14,0x25,0x42,0x1d,0xf3,0x45,0xb0,0x49,0x24,0x99,0xe3,0xad,0xb2,0x0a,0x49,0x7f,0x52,0xa7,0x6a,0x26,0x88,0x88,0x8a,0x5e,0x4c,0xe1,0x70,0x9a,0x72,0xa2,0xda,0x8b,0x31,0xbc,0x38,0xbc,0x8d,0xc2,0x7d,0x3b,0x7c,0x4d,0x45,0x0e,0x5a,0x06,0x0c,0xb5,0x01,0xa4,0x3b,0xa3,0x04,0x6d,0xa9,0x34,0x4c,0x76,0x82,0x07,0x95,0x46,0x1d,0x85,0x11,0x81,0x7f,0x5a,0xc9,0x84,0x66,0x90,0xed,0x5b,0x09,0x7b,0x7d,0xa4,0xf6,0xde,0x33,0x3d,0x1c,0xde,0x7e,0xfb,0xc1,0x6d,0xae,0x03,0x17,0x2a,0x87,0x69,0x5f,0x5f,0x78,0xd3,0x32,0x2a,0x44,0x2e,0x67,0x8b,0x4d,0x1a,0x11,0xd9,0x18,0x3b,0xd3,0xda,0x57,0x41,0x61,0x70,0x5f,0xae,0x44,0x39,0xe1,0x4e,0xe1,0xa7,0x7c,0x6b,0xfc,0xf7,0xe7,0x9f,0x7e,0x94,0x32,0xff,0x0f,0xff,0x73,0xc3,0x4b,0x69,0x43,0xec,0x64,0x29,0x82,0x1f,0xfb,0xcd,0x4a,0xac,0x1b,0xf9,0xbe,0x4f,0x33,0x05,0x0f,0xe2,0xc7,0x8f,0x32,0x90,0xbc,0xd3,0x31,0x87,0x83,0x41,0x2d,0x2e,0x21,0xd9,0x94,0x73,0xee,0x64,0xa6,0xe5,0x71,0x87,0x9b,0x96,0x75,0xa8,0x77,0x02,0x71,0x64,0x45,0x7b,0x2b,0x35,0x7d,0x38,0x1c,0xcd,0xfa,0xc6,0xe4,0xd6,0xbe,0xe0,0x72,0x53,0xa4,0x46,0x9c,0x45,0x9b,0x35,0x72,0xc1,0x59,0x72,0xf9,0x7d,0xc2,0x69,0xf8,0xaf,0xc7,0xdb,0x18,0x1a,0xcf,0xfa,0x65,0xed,0x46,0xea,0x0f,0x6c,0xb4,0x08,0x53,0xc0,0x6d,0x44,0x49,0x50,0x96,0xfe,0x05,0xb3,0x85,0xcf,0x2e,0x0c,0x11,0xfb,0x17,0xb7,0x0c,0xb5,0x5d,0x98,0x6b,0x3c,0xcf,0x98,0xcd,0xa1,0xd7,0x87,0x22,0x86,0x5f,0x30,0x0c,0x0b,0x0c,0x52,0xdf,0x9d,0xa4,0xd3,0xeb,0x49,0xda,0xed,0x5a,0xdf,0x98,0xac,0xdc,0x32,0xeb,0xae,0xba,0xef,0xfa,0xb2,0xcb,0x14,0x63,0x1b,0xac,0xcb,0x0c,0x63,0xad,0x7e,0xd9,0x5d,0x7a,0xdf,0xc5,0x4e,0x53,0x55,0x1b,0xea,0x80,0xf7,0x1f,0x58,0x37,0x85,0xcc,0x90,0x8f,0x39,0xf7,0x2f,0x14,0xd3,0x86,0xd9,0xee,0xc2,0xc8,0xd2,0x28,0x11,0xd1,0x83,0x7f,0x51,0x4a,0xd3,0xc2,0x12,0x55,0x8d,0x06,0x8c,0x79,0x5e,0x33,0x9b,0xf6,0x95,0x74,0xa6,0x05,0x5c,0x99,0x0a,0x7f,0x8e,0xf6,0x28,0x07,0x11,0x9e,0x39,0x63,0x1e,0x93,0xcc,0xce,0x60,0x55,0xd1,0x15,0xb4,0xd8,0x61,0x13,0xb2,0x56,0x96,0xcf,0xe6,0x0a,0x98,0x5a,0x74,0xd7,0x5d,0x33,0x7d,0x7a,0x62,0xca,0x42,0x66,0x75,0x79,0x37,0xeb,0xb2,0xf7,0x4c,0x89,0xe7,0x6c,0x8e,0x7d,0xde,0x2b,0xa9,0x99,0x4e,0xc7,0x73,0x4c,0xfd,0xd6,0x9e,0xfa,0x4d,0x4d,0xd1,0x19,0xa6,0x3e,0xd3,0xe0,0x90,0xe8,0xf3,0x6e,0xdb,0x8a,0xb7,0x86,0xb9,0xfe,0xd6,0x22,0x6d,0x8f,0xe1,0xf3,0xcb,0xe1,0xd9,0xf2,0x2b,0x65,0x39,0x05,0x00,0xc0,0x89,0x78,0x59,0xae,0xf3,0x1a,0x36,0x66,0xc3,0xe2,0x68,0xf1,0x6c,0x30,0x55,0x3e,0xc3,0x9e,0xb3,0xd1,0x9c,0x19,0xa0,0x12,0xa6,0x36,0x5b,0x77,0xd9,0x33,0xbe,0x0c,0xa7,0x33,0x43,0x35,0x77,0x34,0xac,0x75,0x35,0xd6,0x39,0x82,0xbd,0x45,0xb5,0x5c,0xe8,0x75,0xac,0x8d,0xb1,0x5a,0x37,0x63,0xdd,0x3b,0x76,0xfb,0x2b,0xb3,0xd9,0xfb,0x5f,0x3e,0xe2,0xf3,0x07,0xe4,0xeb,0x36,0x78,0xc4,0xe8,0xe3,0x26,0x4c,0x39,0x00,0x65,0x3f,0x7f,0xfb,0x1d,0x3e,0x7f,0xad,0x77,0x62,0xf7,0xca,0x52,0xc3,0x33,0x9a,0xc8,0x30,0xe0,0xf1,0x65,0xd2,0xb6,0xfb,0x0b,0xf0,0x61,0xa1,0x34,0x92,0x56,0x88,0xa3,0x05,0x85,0x38,0xa2,0x3e,0x2e,0x61,0x0a,0x41,0xd2,0x28,0xda,0x49,0x50,0x91,0x46,0x19,0x54,0x5c,0xed,0x68,0xe7,0x89,0xe9,0xb6,0x92,0x19,0xd5,0x61,0x72,0x3b,0xb5,0xa5,0xb5,0x97,0x02,0x35,0xa5,0xae,0xdc,0x4f,0x62,0xcd,0xd1,0x20,0x99,0x90,0x58,0x13,0xca,0x04,0xe1,0xc3,0x10,0xa8,0x32,0x6b,0x42,0x75,0x37,0x78,0xe3,0xfb,0x12,0xdf,0x98,0xf7,0x4b,0x2e,0x1b,0x75,0x28,0xd8,0xf2,0xe9,0x69,0xcc,0x2f,0x2d,0xcb,0x16,0xb0,0x19,0xf9,0x83,0x30,0xa9,0x02,0x34,0x4c,0x3a,0x9e,0x21,0x72,0xb6,0x70,0x14,0x3f,0x38,0x35,0xa5,0xf9,0x7c,0xce,0x54,0x97,0x06,0x30,0x89,0xdd,0xd8,0xb3,0x71,0xc5,0x9f,0x64,0x9b,0xb5,0x17,0x0b,0xf3,0x4d,0x98,0xe8,0xa4,0x94,0xfe,0x56,0xa4,0x71,0xb6,0xbd,0x63,0x3b,0xec,0x78,0xef,0x14,0x13,0xe9,0x64,0x39,0x4f,0x4d,0xf6,0xc3,0xf7,0x9f,0x50,0x54,0x5d,0xd6,0xa1,0xc8,0x82,0x57,0xde,0x03,0x74,0x8b,0x0a,0x99,0xec,0x33,0x11,0x26,0x9f,0xd9,0x6f,0x06,0x96,0x2d,0x9d,0x92,0xa7,0xb1,0x99,0x6e,0x92,0xa4,0x4d,0x03,0x65,0x6e,0x12,0x06,0xf9,0x19,0x06,0xb9,0x65,0xef,0xdc,0x4e,0x67,0xe7,0x82,0x4e,0x02,0x90,0x24,0xaa,0xea,0x79,0x15,0xe0,0x04,0x7b,0xd0,0x5a,0x1b,0x9a,0x6d,0x34,0x5a,0x8c,0x03,0x3f,0x5c,0x3b,0x5a,0xce,0x59,0x3f,0xc8,0x45,0x9f,0xb8,0x8a,0x53,0x2e,0xf7,0xd1,0xff,0x2d,0x04,0xc4,0xc4,0x55,0x73,0x77,0xe0,0xb9,0x40,0xae,0xb5,0x35,0x11,0x0d,0x70,0xcf,0x41,0x0c,0x4e,0x15,0x24,0x1b,0xee,0xfb,0xd1,0xd2,0xc9,0xe7,0xa6,0xa8,0xfc,0x37,0x03,0x7b,0x83,0xc8,0xda,0x14,0x3f,0x4b,0x07,0x15,0xc4,0x87,0x07,0xb6,0x2d,0xb2,0x74,0x69,0x34,0x29,0xaa,0x5c,0x6e,0x39,0x59,0x35,0x1c,0x8c,0x98,0xa3,0x54,0xfa,0x73,0x4d,0x53,0x12,0x15,0x24,0x51,0x41,0x92,0x6a,0x1f,0x70,0x23,0xe9,0x9b,0x0a,0x92,0xf7,0x60,0x2a,0xe2,0x48,0x5b,0xa2,0x70,0xdf,0xa4,0xda,0x94,0xa7,0xa7,0x66,0xe4,0xac,0x03,0x19,0xad,0x4c,0x39,0xbd,0x9c,0xf7,0x3f,0xc7,0x9f,0xe3,0x39,0xfd,0x3a,0x5f,0x1b,0xf5,0x3d,0x39,0x1d,0xcd,0xfb,0xbf,0x9b,0xa6,0x3b,0xbc,0x79,0x72,0xdf,0x0d,0x9f,0x86,0xc3,0xd1,0x13,0xda,0x17,0xfc,0xdd,0x3c,0x0d,0xc7,0x78,0x1e,0x8f,0xac,0xcf,0xce,0x40,0xff,0x5a,0x4f,0xe6,0x70,0x3c,0xfe,0xec,0x98,0xa6,0x39,0x78,0xfa,0xfb,0x15,0xff,0x8f,0xba,0xf5,0xa4,0x55,0xbf,0xae,0x48,0x3f,0xdf,0xf4,0x3d,0x18,0x7c,0x37,0xe8,0xbd,0x0b,0x7a,0x8b,0xfb,0xbd,0x6b,0x0f,0x0f,0x77,0x9f,0x1d,0xaf,0x77,0x6f,0xed,0xc7,0x87,0x97,0x72,0xe8,0x0a,0xcb,0xab,0xd1,0x41,0x95,0x34,0x38,0x25,0x3c,0x5d,0xca,0xd5,0xf4,0xf2,0x0c,0xba,0xfe,0xef,0x7a,0xfd,0x97,0xfb,0x2e,0x2d,0xb5,0xac,0xfa,0x32,0xc2,0xfb,0x25,0x92,0x8d,0xfd,0x8a,0x74,0x2c,0xb9,0xa1,0x3a,0x50,0x23,0x30,0xb0,0x12,0x8d,0xd9,0x09,0xc1,0x2c,0x8f,0x04,0x53,0xb6,0x08,0xc6,0x40,0x73,0x50,0xf0,0xb2,0xc4,0x53,0x93,0x0f,0xa0,0x4f,0xbc,0xc0,0xae,0x37,0x6b,0xe3,0xd2,0x88,0x56,0x78,0x03,0x8c,0xb0,0x6b,0x69,0x1b,0x09,0x97,0x34,0x30,0x82,0x34,0x46,0x83,0xbb,0x14,0xe8,0x73,0x71,0x09,0x3f,0x5a,0x20,0x26,0x79,0x6f,0xd9,0xa9,0xb3,0xc0,0xcd,0x88,0x84,0xb4,0xdf,0xb8,0x13,0xd1,0xf5,0x39,0x55,0x53,0xb7,0x76,0x83,0x4a,0xee,0x90,0x20,0xfb,0x91,0xac,0xa2,0xc9,0x55,0x1b,0x49,0x3f,0xb4,0x85,0x65,0x87,0x09,0x92,0xf5,0x39,0x03,0x29,0x6b,0xf7,0x94,0x72,0xba,0x13,0x18,0x4c,0xf8,0x74,0x34,0xe1,0xea,0x0a,0xbc,0x63,0x39,0x99,0x1a,0x85,0xf8,0xa8,0xb6,0x34,0x5a,0xe0,0x78,0x7e,0x6f,0x9d,0x10,0xc6,0x9d,0xa8,0xe6,0x9a,0x2a,0xbc,0x9a,0x39,0x6c,0x92,0xbc,0x20,0x11,0x3b,0xaa,0x80,0xf3,0xb9,0x62,0x54,0x75,0x3a,0x67,0xca,0x74,0x48,0xab,0x46,0x24,0x95,0x34,0x45,0x2e,0x28,0xc9,0x25,0xf4,0x37,0x47,0xa7,0x5e,0x3e,0xfa,0x60,0x7f,0xaa,0x3d,0x4d,0x92,0x29,0x75,0x03,0xba,0xc2,0xb9,0xac,0xeb,0x4a,0xdf,0xf1,0xbe,0xbe,0x9a,0x64,0xd7,0x67,0xf5,0x0d,0x0c,0xf0,0x4c,0xec,0xa5,0x9e,0x2c,0xa7,0x7e,0x4b,0x9a,0x33,0x17,0x16,0x0d,0xe8,0x46,0xe9,0xe8,0xe5,0xc0,0xf0,0xd2,0x96,0x5d,0x18,0x32,0xa7,0x73,0xb1,0x2a,0x28,0xe9,0xca,0xb1,0xe8,0xa8,0xa6,0x83,0x38,0xdf,0xdd,0xfb,0x9b,0xdd,0x8d,0x16,0xc7,0xd2,0x0d,0xb2,0x8f,0x40,0x28,0x51,0xa5,0x18,0xe5,0xa0,0x48,0x56,0xe4,0xa0,0x33,0x0e,0xce,0x02,0xb2,0xc0,0xcc,0x0e,0x4a,0x5b,0xd8,0x8b,0x08,0x1d,0x0f,0x85,0xd3,0xb5,0x29,0xda,0x30,0xe0,0x01,0x82,0x9d,0xab,0xba,0xb8,0x9d,0xbd,0x1b,0x36,0x83,0xcb,0x7a,0x50,0x81,0x45,0x52,0x34,0x60,0x3f,0x7e,0xfa,0xf9,0x27,0x36,0x01,0x81,0x66,0xed,0x3e,0x8c,0xb6,0x9b,0x34,0x5c,0x24,0xfd,0x7f,0x7f,0xfc,0xf0,0x8b,0x93,0x07,0x45,0xc9,0x4d,0x45,0xb5,0x48,0xdc,0x3c,0x4b,0x4b,0xfe,0x09,0xaf,0x0e,0xd6,0x84,0xae,0x80,0x68,0x09,0x4b,0x97,0xbe,0x54,0xd0,0xd0,0x6b,0x82,0x21,0x2d,0x22,0xa6,0x3a,0x18,0x12,0x51,0x9c,0xd4,0xa5,0x43,0x0c,0x29,0x9d,0x5c,0x73,0x64,0x95,0x89,0x18,0x9c,0xd9,0xe9,0x68,0x9a,0x3c,0xb4,0x96,0x63,0x5b,0xb4,0x25,0xe8,0x21,0xef,0x06,0xf7,0x7a,0xaf,0x1a,0x2d,0x1f,0x68,0xf9,0x7a,0x4b,0x9e,0xa0,0xfa,0x54,0x30,0xd5,0x1a,0x4a,0x48,0x68,0x52,0x23,0x04,0x74,0xe9,0x96,0x23,0x11,0xae,0xef,0x09,0x65,0xc2,0x01,0x78,0x38,0xbc,0xed,0x66,0xb7,0xbb,0x88,0x66,0xc3,0x4e,0x47,0x91,0xf3,0xce,0xad,0x33,0x06,0x7a,0xc3,0x36,0x1c,0xcd,0x25,0x52,0x5f,0xf8,0x27,0x19,0x5f,0xe7,0x34,0x26,0xab,0xed,0x57,0x26,0xcb,0xea,0x7c,0xb2,0xce,0x7b,0xa4,0xe4,0x2d,0x11,0x08,0xa0,0x6a,0xdf,0x4c,0xfa,0x2e,0x75,0xa0,0x13,0x90,0xc4,0x67,0x2b,0x74,0xe2,0x5e,0xbf,0xcf,0xba,0x88,0xf0,0xc1,0x1e,0xd2,0x85,0x44,0x96,0xbe,0xf0,0x48,0x7b,0x32,0x64,0x56,0x9d,0x0c,0xba,0xa5,0x40,0xd8,0x5f,0xc6,0x17,0xd5,0xc3,0x98,0x12,0xa3,0x4b,0x90,0x78,0x2b,0xd0,0xaf,0x03,0x53,0xfa,0x77,0x91,0x51,0x77,0xf8,0x60,0x94,0x59,0xab,0x81,0x66,0xfa,0x5f,0x6f,0xac,0x2d,0x53,0xea,0xea,0xd5,0xd1,0xd7,0x6f,0xe1,0xed,0x49,0xfd,0x1f,0xb6,0xdb,0x0f,0xbf,0x7c,0x68,0xef,0x52,0x18,0xa1,0xc4,0xfd,0xe5,0x47,0xe1,0xb1,0x0f,0xa2,0x34,0x57,0x0b,0x67,0xba,0x0f,0xff,0x8b,0x61,0xff,0xa5,0xb8,0xde,0x30,0xa7,0x86,0xb0,0xac,0x66,0x1f,0x83,0x0a,0xef,0x5a,0x86,0xbe,0xb6,0x37,0x85,0x82,0xcc,0x46,0x0b,0xa0,0xf8,0x79,0x1b,0x08,0xe9,0x38,0xce,0xeb,0x8b,0xf3,0xb2,0x2d,0xa2,0xf6,0xb3,0xd5,0xca,0xe7,0x5b,0xdd,0x67,0xe2,0x3b,0x4b,0x1f,0xf8,0x63,0x4e,0x9c,0x8d,0x04,0xbc,0x44,0x5e,0x56,0xf4,0x32,0x02,0xd9,0x77,0x59,0x0c,0x46,0xa2,0x96,0x80,0xad,0x83,0x9d,0xbe,0x4a,0xfc,0x1b,0xdd,0x8f,0x36,0xdc,0xde,0x6e,0x43,0xf3,0xed,0xac,0xe9,0x2d,0x0d,0xaf,0xe9,0x29,0x6b,0xdb,0xc2,0x17,0x10,0x6a,0xb4,0x1a,0x9c,0xe8,0x84,0x59,0xf6,0xd0,0xd2,0x7c,0xd5,0x9f,0x68,0xf1,0x17,0x13,0xd5,0xf6,0x95,0x58,0x11,0x7a,0xdb,0x66,0xc1,0xea,0xc5,0xe9,0xf4,0x0f,0x15,0x9a,0x96,0xe5,0xeb,0xf3,0x2c,0xc8,0x8c,0xe4,0x45,0x83,0x0d,0x1e,0x9c,0xe1,0xcf,0xf0,0x3a,0x69,0x58,0xe6,0x93,0xa3,0x6f,0x47,0x38,0x69,0x52,0xe1,0x49,0x83,0xbf,0x01,0x14,0x5c,0xef,0x5a,0x6c,0x66,0xfc,0x76,0xe6,0x8d,0x82,0xc5,0x88,0x8e,0xc0,0x28,0xcd,0x19,0xca,0xe8,0x2b,0xd0,0x60,0x0d,0x35,0xc9,0xb0,0x01,0x5f,0xaf,0xa4,0xd4,0x3f,0xfe,0x07,0x56,0xaf,0x35,0xf1,0x5e,0x16,0x00,0x00}; 101 | webServer.writeP(page, sizeof(page)); 102 | 103 | } else { 104 | if (tailComplete) { 105 | char name[8]; 106 | char value[30]; 107 | URLPARAM_RESULT rc; 108 | 109 | while (strlen(urlTail)) { 110 | rc = webServer.nextURLparam(&urlTail, name, 8, value, 30); 111 | if (rc == URLPARAM_EOS) { 112 | webServer.httpFail(); 113 | return; 114 | } 115 | 116 | if (strcmp(name, "i") == 0) { 117 | setField(ip, value, '.', 10); 118 | } else if (strcmp(name, "d") == 0) { 119 | setField(dnsa, value, '.', 10); 120 | } else if (strcmp(name, "g") == 0) { 121 | setField(gateway, value, '.', 10); 122 | } else if (strcmp(name, "s") == 0) { 123 | setField(subnet, value, '.', 10); 124 | } else if (strcmp(name, "m") == 0) { 125 | setField(mac, value, ':', 16); 126 | } else if (strcmp(name, "p") == 0) { 127 | for (int i = 0; i < 9; i++) { 128 | pwd[i] = '\0'; 129 | } 130 | strncpy(pwd, value, 8); 131 | } 132 | } 133 | 134 | byte checksum = 7; 135 | int a = 0; 136 | for (; a < 4; a++) { 137 | EEPROM.write(a, ip[a]); 138 | checksum ^= ip[a]; 139 | } 140 | for (; a < 8; a++) { 141 | EEPROM.write(a, dnsa[a - 4]); 142 | checksum ^= dnsa[a - 4]; 143 | } 144 | for (; a < 12; a++) { 145 | EEPROM.write(a, gateway[a - 8]); 146 | checksum ^= gateway[a - 8]; 147 | } 148 | for (; a < 16; a++) { 149 | EEPROM.write(a, subnet[a - 12]); 150 | checksum ^= subnet[a - 12]; 151 | } 152 | for (; a < 22; a++) { 153 | EEPROM.write(a, mac[a - 16]); 154 | checksum ^= mac[a - 16]; 155 | } 156 | for (; a < 38; a+=2) { 157 | char c = pwd[(a - 22) / 2]; 158 | byte l = c & 0xff; 159 | byte h = (c >> 8) & 0xff; 160 | EEPROM.write(a, l); 161 | EEPROM.write(a + 1, h); 162 | checksum ^= l; 163 | checksum ^= h; 164 | } 165 | EEPROM.write(a, checksum); 166 | 167 | webServer.httpSuccess(); 168 | webServer.flushBuf(); 169 | webServer.reset(); 170 | 171 | delay(500); 172 | 173 | Ethernet.begin(mac, ip, dnsa, gateway, subnet); 174 | 175 | } else { 176 | webServer.httpFail(); 177 | } 178 | } 179 | } 180 | 181 | void setField(byte *ip, char *value, char sep, int base) { 182 | int p = 0; 183 | int g = 0; 184 | for (int c = 0; c < 30; c++) { 185 | if (value[c] == sep || value[c] == '\0') { 186 | char n[4]; 187 | int i = p; 188 | for (; i < c; i++) { 189 | n[i - p] = value[i]; 190 | } 191 | n[i - p] = '\0'; 192 | ip[g] = strtoul(n, NULL, base); 193 | if (value[c] == '\0') { 194 | break; 195 | } 196 | p = c + 1; 197 | g++; 198 | } 199 | } 200 | } 201 | 202 | void setup() { 203 | Iono.setup(); 204 | pinMode(2, INPUT); 205 | pinMode(3, OUTPUT); 206 | boolean reset = true; 207 | for (int i = 0; i < 5; i++) { 208 | digitalWrite(3, i % 2); 209 | if (digitalRead(2) != i % 2) { 210 | reset = false; 211 | } 212 | } 213 | pinMode(3, INPUT); 214 | 215 | if (!reset) { 216 | byte checksum = 7; 217 | int a = 0; 218 | for (; a < 4; a++) { 219 | ip[a] = EEPROM.read(a); 220 | checksum ^= ip[a]; 221 | } 222 | for (; a < 8; a++) { 223 | dnsa[a - 4] = EEPROM.read(a); 224 | checksum ^= dnsa[a - 4]; 225 | } 226 | for (; a < 12; a++) { 227 | gateway[a - 8] = EEPROM.read(a); 228 | checksum ^= gateway[a - 8]; 229 | } 230 | for (; a < 16; a++) { 231 | subnet[a - 12] = EEPROM.read(a); 232 | checksum ^= subnet[a - 12]; 233 | } 234 | for (; a < 22; a++) { 235 | mac[a - 16] = EEPROM.read(a); 236 | checksum ^= mac[a - 16]; 237 | } 238 | for (; a < 38; a+=2) { 239 | byte l = EEPROM.read(a); 240 | byte h = EEPROM.read(a + 1); 241 | pwd[(a - 22) / 2] = (h << 8) + l; 242 | checksum ^= l; 243 | checksum ^= h; 244 | } 245 | 246 | if (EEPROM.read(a) != checksum) { 247 | reset = true; 248 | } 249 | } 250 | 251 | if (reset) { 252 | ip[0] = 192; 253 | ip[1] = 168; 254 | ip[2] = 0; 255 | ip[3] = 100; 256 | 257 | dnsa[0] = 192; 258 | dnsa[1] = 168; 259 | dnsa[2] = 0; 260 | dnsa[3] = 1; 261 | 262 | gateway[0] = 192; 263 | gateway[1] = 168; 264 | gateway[2] = 0; 265 | gateway[3] = 1; 266 | 267 | subnet[0] = 255; 268 | subnet[1] = 255; 269 | subnet[2] = 255; 270 | subnet[3] = 0; 271 | 272 | mac[0] = 0xDE; 273 | mac[1] = 0xAD; 274 | mac[2] = 0xBE; 275 | mac[3] = 0xEF; 276 | mac[4] = 0xFE; 277 | mac[5] = 0xED; 278 | 279 | pwd[0] = '\0'; 280 | } 281 | 282 | Ethernet.begin(mac, ip, dnsa, gateway, subnet); 283 | 284 | IonoWeb.begin(80); 285 | WebServer &webServer = IonoWeb.getWebServer(); 286 | webServer.setDefaultCommand(&webPageCmd); 287 | webServer.addCommand("config", &configCmd); 288 | } 289 | 290 | void loop() { 291 | // Process incoming requests 292 | IonoWeb.processRequest(); 293 | // Check all the inputs - needed for subscribe 294 | Iono.process(); 295 | } 296 | -------------------------------------------------------------------------------- /examples/ModbusRtuApp/ModbusRtuApp.ino: -------------------------------------------------------------------------------- 1 | /* 2 | ModbusRtuApp.ino - A configurable Modbus RTU Slave for Iono Uno, Iono MKR and Iono RP 3 | 4 | Copyright (C) 2016-2022 Sfera Labs S.r.l. - All rights reserved. 5 | 6 | For information, see: 7 | https://www.sferalabs.cc/ 8 | 9 | This code is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | See file LICENSE.txt for further informations on licensing terms. 14 | */ 15 | 16 | #include 17 | 18 | #ifdef ARDUINO_ARCH_SAMD 19 | #include 20 | #else 21 | #include 22 | #endif 23 | 24 | #ifdef IONO_RP 25 | #include "hardware/watchdog.h" 26 | #endif 27 | 28 | #define DELAY 25 // the debounce delay in milliseconds 29 | #define BOOT_CONSOLE_TIMEOUT_MILLIS 15000 // if 5 consecutive spaces are received within this time interval after boot, enter console mode 30 | 31 | const PROGMEM char CONSOLE_MENU_HEADER[] = {"=== Sfera Labs - Modbus RTU Slave configuration menu - v6.1 ==="}; 32 | const PROGMEM char CONSOLE_MENU_CURRENT_CONFIG[] = {"Print current configuration"}; 33 | const PROGMEM char CONSOLE_MENU_SPEED[] = {"Speed (baud)"}; 34 | const PROGMEM char CONSOLE_MENU_PARITY[] = {"Parity"}; 35 | const PROGMEM char CONSOLE_MENU_MIRROR[] = {"Input/Output rules"}; 36 | const PROGMEM char CONSOLE_MENU_ADDRESS[] = {"Modbus device address"}; 37 | const PROGMEM char CONSOLE_MENU_SAVE[] = {"Save configuration and restart"}; 38 | const PROGMEM char CONSOLE_MENU_TYPE[] = {"Type a menu number (0, 1, 2, 3, 4, 5): "}; 39 | const PROGMEM char CONSOLE_TYPE_SPEED[] = {"Type serial port speed (1: 1200, 2: 2400, 3: 4800, 4: 9600; 5: 19200; 6: 38400, 7: 57600, 8: 115200): "}; 40 | const PROGMEM char CONSOLE_TYPE_PARITY[] = {"Type serial port parity (1: Even, 2: Odd, 3: None): "}; 41 | const PROGMEM char CONSOLE_TYPE_ADDRESS[] = {"Type Modbus device address (1-247): "}; 42 | #ifdef IONO_ARDUINO 43 | #define IORULES_LEN 6 44 | const PROGMEM char CONSOLE_TYPE_MIRROR[] = {"Type Input/Output rules (xxxxxx, F: follow, I: invert, H: flip on L>H transition, L: flip on H>L transition, T: flip on any transition, -: no rule): "}; 45 | #else 46 | #define IORULES_LEN 4 47 | const PROGMEM char CONSOLE_TYPE_MIRROR[] = {"Type Input/Output rules (xxxx, F: follow, I: invert, H: flip on L>H transition, L: flip on H>L transition, T: flip on any transition, -: no rule): "}; 48 | #endif 49 | const PROGMEM char CONSOLE_TYPE_SAVE[] = {"Confirm? (Y/N): "}; 50 | const PROGMEM char CONSOLE_CURRENT_CONFIG[] = {"Current configuration:"}; 51 | const PROGMEM char CONSOLE_NEW_CONFIG[] = {"New configuration:"}; 52 | const PROGMEM char CONSOLE_ERROR[] = {"Error"}; 53 | const PROGMEM char CONSOLE_SAVED[] = {"Saved"}; 54 | 55 | #ifndef SERIAL_PORT_MONITOR 56 | #define SERIAL_PORT_MONITOR SERIAL_PORT_HARDWARE 57 | #endif 58 | 59 | const long SPEED_VALUE[] = {0, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200}; 60 | 61 | byte consoleState = 0; // 0: wait for menu selection number; 1: speed; 2: parity; 3: address; 4: i/o rules; 5 confirm to save 62 | byte opMode = 0; // 0: boot sequence, wait to enter console mode; 1: console mode; 2: Modbus slave mode 63 | boolean validConfiguration; 64 | unsigned long bootTimeMillis; 65 | short spacesCounter = 0; 66 | char consoleInputBuffer[7]; 67 | 68 | byte speedCurrent; 69 | byte parityCurrent; 70 | byte addressCurrent; 71 | char rulesCurrent[7]; 72 | byte speedNew; 73 | byte parityNew; 74 | byte addressNew; 75 | char rulesNew[IORULES_LEN + 1]; 76 | 77 | Stream *consolePort = NULL; 78 | 79 | bool configReset = false; 80 | 81 | void setup() { 82 | bootTimeMillis = millis(); 83 | 84 | Iono.setup(); 85 | 86 | SERIAL_PORT_HARDWARE.begin(9600); 87 | if ((int) &SERIAL_PORT_HARDWARE != (int) &SERIAL_PORT_MONITOR) { 88 | SERIAL_PORT_MONITOR.begin(9600); 89 | } 90 | 91 | // retrieve settings from EEPROM 92 | validConfiguration = getEEPROMConfig(); 93 | } 94 | 95 | void loop() { 96 | if (opMode == 2) { 97 | IonoModbusRtuSlave.process(); 98 | 99 | if (configReset) { 100 | delay(1000); 101 | softReset(); 102 | } 103 | 104 | } else { 105 | if (consolePort == NULL) { 106 | if (SERIAL_PORT_HARDWARE.available()) { 107 | consolePort = &SERIAL_PORT_HARDWARE; 108 | } else if (SERIAL_PORT_MONITOR.available()) { 109 | consolePort = &SERIAL_PORT_MONITOR; 110 | } 111 | } else if (consolePort->available()) { 112 | int b = consolePort->read(); 113 | if (opMode == 0) { 114 | if (b == ' ') { 115 | if (spacesCounter >= 4) { 116 | delay(50); 117 | Iono.serialTxEn(true); 118 | printConsoleMenu(); 119 | consolePort->flush(); 120 | Iono.serialTxEn(false); 121 | opMode = 1; 122 | } else { 123 | spacesCounter++; 124 | } 125 | } else if (validConfiguration) { 126 | startModbus(); 127 | } else { 128 | consolePort = NULL; 129 | } 130 | } else { // opMode == 1 131 | serialConsole(b); 132 | } 133 | } 134 | 135 | if (validConfiguration && opMode == 0 && bootTimeMillis + BOOT_CONSOLE_TIMEOUT_MILLIS < millis()) { 136 | startModbus(); 137 | } 138 | } 139 | } 140 | 141 | void startModbus() { 142 | SERIAL_PORT_HARDWARE.end(); 143 | SERIAL_PORT_MONITOR.end(); 144 | 145 | switch (parityCurrent) { 146 | case 2: 147 | IonoModbusRtuSlave.begin(addressCurrent, SPEED_VALUE[speedCurrent], SERIAL_8O1, DELAY); 148 | break; 149 | case 3: 150 | IonoModbusRtuSlave.begin(addressCurrent, SPEED_VALUE[speedCurrent], SERIAL_8N2, DELAY); 151 | break; 152 | default: 153 | IonoModbusRtuSlave.begin(addressCurrent, SPEED_VALUE[speedCurrent], SERIAL_8E1, DELAY); 154 | } 155 | 156 | IonoModbusRtuSlave.setCustomHandler(&modbusConfigHandler); 157 | 158 | if (rulesCurrent[0] != 0) { 159 | setLink(rulesCurrent[0], DI1, DO1); 160 | setLink(rulesCurrent[1], DI2, DO2); 161 | setLink(rulesCurrent[2], DI3, DO3); 162 | setLink(rulesCurrent[3], DI4, DO4); 163 | #ifdef IONO_ARDUINO 164 | setLink(rulesCurrent[4], DI5, DO5); 165 | setLink(rulesCurrent[5], DI6, DO6); 166 | #endif 167 | } 168 | 169 | opMode = 2; 170 | } 171 | 172 | byte modbusConfigHandler(byte unitAddr, byte function, word regAddr, word qty, byte *data) { 173 | switch (function) { 174 | case MB_FC_WRITE_MULTIPLE_REGISTERS: 175 | case MB_FC_WRITE_SINGLE_REGISTER: 176 | if (regAddr == 3000) { 177 | word val = ModbusRtuSlave.getDataRegister(function, data, 0); 178 | if (qty != 1 || val != 0xABCD) { 179 | return MB_EX_ILLEGAL_DATA_VALUE; 180 | } 181 | if (saveConfig()) { 182 | configReset = true; 183 | return MB_RESP_OK; 184 | } else { 185 | return MB_EX_SERVER_DEVICE_FAILURE; 186 | } 187 | } 188 | if (modbusCheckAddrRange(regAddr, qty, 3001, 3004 + IORULES_LEN - 1)) { 189 | for (word i = regAddr; i < regAddr + qty; i++) { 190 | word val = ModbusRtuSlave.getDataRegister(function, data, i - regAddr); 191 | if (i == 3001) { 192 | if (val < 1 || val > 247) { 193 | return MB_EX_ILLEGAL_DATA_VALUE; 194 | } 195 | addressNew = val; 196 | } else if (i == 3002) { 197 | if (val < 1 || val > 8) { 198 | return MB_EX_ILLEGAL_DATA_VALUE; 199 | } 200 | speedNew = val; 201 | } else if (i == 3003) { 202 | if (val < 1 || val > 3) { 203 | return MB_EX_ILLEGAL_DATA_VALUE; 204 | } 205 | parityNew = val; 206 | } else if (i >= 3004) { 207 | if (val != '-' && val != 'F' && val != 'I' && val != 'H' && val != 'L' && val != 'T') { 208 | return MB_EX_ILLEGAL_DATA_VALUE; 209 | } 210 | rulesNew[i - 3004] = val; 211 | } 212 | } 213 | return MB_RESP_OK; 214 | } 215 | break; 216 | 217 | case MB_FC_READ_HOLDING_REGISTERS: 218 | if (modbusCheckAddrRange(regAddr, qty, 3001, 3004 + IORULES_LEN - 1)) { 219 | for (word i = regAddr; i < regAddr + qty; i++) { 220 | if (i == 3001) { 221 | ModbusRtuSlave.responseAddRegister(((addressNew == 0) ? addressCurrent : addressNew) & 0xff); 222 | } else if (i == 3002) { 223 | ModbusRtuSlave.responseAddRegister(((speedNew == 0) ? speedCurrent : speedNew) & 0xff); 224 | } else if (i == 3003) { 225 | ModbusRtuSlave.responseAddRegister(((parityNew == 0) ? parityCurrent : parityNew) & 0xff); 226 | } else if (i >= 3004) { 227 | ModbusRtuSlave.responseAddRegister(((rulesNew[i - 3004] == 0) ? rulesCurrent[i - 3004] : rulesNew[i - 3004]) & 0xff); 228 | } 229 | } 230 | return MB_RESP_OK; 231 | } 232 | break; 233 | 234 | default: 235 | break; 236 | } 237 | 238 | return MB_RESP_PASS; 239 | } 240 | 241 | bool modbusCheckAddrRange(word regAddr, word qty, word min, word max) { 242 | return regAddr >= min && regAddr <= max && regAddr + qty <= max + 1; 243 | } 244 | 245 | void setLink(char rule, uint8_t dix, uint8_t dox) { 246 | switch (rule) { 247 | case 'F': 248 | Iono.linkDiDo(dix, dox, LINK_FOLLOW, DELAY); 249 | break; 250 | case 'I': 251 | Iono.linkDiDo(dix, dox, LINK_INVERT, DELAY); 252 | break; 253 | case 'T': 254 | Iono.linkDiDo(dix, dox, LINK_FLIP_T, DELAY); 255 | break; 256 | case 'H': 257 | Iono.linkDiDo(dix, dox, LINK_FLIP_H, DELAY); 258 | break; 259 | case 'L': 260 | Iono.linkDiDo(dix, dox, LINK_FLIP_L, DELAY); 261 | break; 262 | default: 263 | break; 264 | } 265 | } 266 | 267 | void serialConsole(int b) { 268 | Iono.serialTxEn(true); 269 | delayMicroseconds(4000); // this is to let the console also work over the RS485 interface 270 | switch (consoleState) { 271 | case 0: // waiting for menu selection number 272 | switch (b) { 273 | case '0': 274 | consolePort->println((char)b); 275 | consolePort->println(); 276 | printlnProgMemString(CONSOLE_CURRENT_CONFIG); 277 | printConfiguration(speedCurrent, parityCurrent, addressCurrent, rulesCurrent); 278 | printConsoleMenu(); 279 | break; 280 | case '1': 281 | consoleState = 1; 282 | consoleInputBuffer[0] = 0; 283 | consolePort->println((char)b); 284 | consolePort->println(); 285 | printProgMemString(CONSOLE_TYPE_SPEED); 286 | break; 287 | case '2': 288 | consoleState = 2; 289 | consoleInputBuffer[0] = 0; 290 | consolePort->println((char)b); 291 | consolePort->println(); 292 | printProgMemString(CONSOLE_TYPE_PARITY); 293 | break; 294 | case '3': 295 | consoleState = 3; 296 | consoleInputBuffer[0] = 0; 297 | consolePort->println((char)b); 298 | consolePort->println(); 299 | printProgMemString(CONSOLE_TYPE_ADDRESS); 300 | break; 301 | case '4': 302 | consoleState = 4; 303 | consoleInputBuffer[0] = 0; 304 | consolePort->println((char)b); 305 | consolePort->println(); 306 | printProgMemString(CONSOLE_TYPE_MIRROR); 307 | break; 308 | case '5': 309 | consoleState = 5; 310 | consolePort->println((char)b); 311 | consolePort->println(); 312 | printlnProgMemString(CONSOLE_NEW_CONFIG); 313 | printConfiguration((speedNew == 0) ? speedCurrent : speedNew, (parityNew == 0) ? parityCurrent : parityNew, (addressNew == 0) ? addressCurrent : addressNew, (rulesNew[0] == 0) ? rulesCurrent : rulesNew); 314 | printProgMemString(CONSOLE_TYPE_SAVE); 315 | break; 316 | } 317 | break; 318 | case 1: // speed 319 | if (numberEdit(consoleInputBuffer, &speedNew, b, 1, 1, 8)) { 320 | consoleState = 0; 321 | consolePort->println(); 322 | printConsoleMenu(); 323 | } 324 | break; 325 | case 2: // parity 326 | if (numberEdit(consoleInputBuffer, &parityNew, b, 1, 1, 3)) { 327 | consoleState = 0; 328 | consolePort->println(); 329 | printConsoleMenu(); 330 | } 331 | break; 332 | case 3: // address 333 | if (numberEdit(consoleInputBuffer, &addressNew, b, 3, 1, 247)) { 334 | consoleState = 0; 335 | consolePort->println(); 336 | printConsoleMenu(); 337 | } 338 | break; 339 | case 4: // rules 340 | if (rulesEdit(consoleInputBuffer, rulesNew, b, IORULES_LEN)) { 341 | consoleState = 0; 342 | consolePort->println(); 343 | printConsoleMenu(); 344 | } 345 | break; 346 | case 5: // confirm to save 347 | switch (b) { 348 | case 'Y': 349 | case 'y': 350 | consoleState = 0; 351 | consolePort->println('Y'); 352 | if (saveConfig()) { 353 | printlnProgMemString(CONSOLE_SAVED); 354 | delay(1000); 355 | softReset(); 356 | } else { 357 | printlnProgMemString(CONSOLE_ERROR); 358 | } 359 | printConsoleMenu(); 360 | break; 361 | case 'N': 362 | case 'n': 363 | consoleState = 0; 364 | consolePort->println('N'); 365 | consolePort->println(); 366 | printConsoleMenu(); 367 | break; 368 | } 369 | break; 370 | default: 371 | break; 372 | } 373 | consolePort->flush(); 374 | Iono.serialTxEn(false); 375 | } 376 | 377 | boolean saveConfig() { 378 | if (speedNew == 0) { 379 | speedNew = speedCurrent; 380 | } 381 | if (parityNew == 0) { 382 | parityNew = parityCurrent; 383 | } 384 | if (addressNew == 0) { 385 | addressNew = addressCurrent; 386 | } 387 | for (int i = 0; i < IORULES_LEN; i++) { 388 | if (rulesNew[i] == 0) { 389 | rulesNew[i] = rulesCurrent[i]; 390 | } 391 | } 392 | return writeEepromConfig(speedNew, parityNew, addressNew, rulesNew); 393 | } 394 | 395 | boolean writeEepromConfig(byte speed, byte parity, byte address, char *rules) { 396 | byte checksum = 7; 397 | if (speed != 0 && parity != 0 && address != 0) { 398 | EEPROM.write(0, speed); 399 | checksum ^= speed; 400 | EEPROM.write(1, parity); 401 | checksum ^= parity; 402 | EEPROM.write(2, address); 403 | checksum ^= address; 404 | for (int a = 0; a < IORULES_LEN; a++) { 405 | EEPROM.write(a + 3, rules[a]); 406 | checksum ^= rules[a]; 407 | } 408 | EEPROM.write(9, checksum); 409 | #if defined(ARDUINO_ARCH_SAMD) || defined(IONO_RP) 410 | EEPROM.commit(); 411 | #endif 412 | return true; 413 | } else { 414 | return false; 415 | } 416 | } 417 | 418 | boolean readEepromConfig(byte *speedp, byte *parityp, byte *addressp, char *rulesp) { 419 | byte checksum = 7; 420 | 421 | #ifdef ARDUINO_ARCH_SAMD 422 | if (!EEPROM.isValid()) { 423 | return false; 424 | } 425 | #endif 426 | *speedp = EEPROM.read(0); 427 | checksum ^= *speedp; 428 | *parityp = EEPROM.read(1); 429 | checksum ^= *parityp; 430 | *addressp = EEPROM.read(2); 431 | checksum ^= *addressp; 432 | for (int a = 0; a < IORULES_LEN; a++) { 433 | rulesp[a] = EEPROM.read(a + 3); 434 | checksum ^= rulesp[a]; 435 | } 436 | return (EEPROM.read(9) == checksum); 437 | } 438 | 439 | boolean getEEPROMConfig() { 440 | #ifdef IONO_RP 441 | EEPROM.begin(256); 442 | #endif 443 | if (!readEepromConfig(&speedCurrent, &parityCurrent, &addressCurrent, rulesCurrent)) { 444 | speedCurrent = 0; 445 | parityCurrent = 0; 446 | addressCurrent = 0; 447 | for (int i = 0; i < IORULES_LEN; i++) { 448 | rulesCurrent[i] = '-'; 449 | } 450 | } 451 | return (speedCurrent != 0 && parityCurrent != 0 && addressCurrent != 0); 452 | } 453 | 454 | void softReset() { 455 | #ifdef IONO_RP 456 | watchdog_enable(10, 1); 457 | while (1); 458 | #elif defined(ARDUINO_ARCH_SAMD) 459 | NVIC_SystemReset(); 460 | #else 461 | asm volatile (" jmp 0"); 462 | #endif 463 | } 464 | 465 | void printlnProgMemString(const char* s) { 466 | printProgMemString(s); 467 | consolePort->println(); 468 | } 469 | 470 | void printProgMemString(const char* s) { 471 | int len = strlen_P(s); 472 | for (int k = 0; k < len; k++) { 473 | consolePort->print((char)pgm_read_byte_near(s + k)); 474 | } 475 | } 476 | 477 | void printConsoleMenu() { 478 | consolePort->println(); 479 | printlnProgMemString(CONSOLE_MENU_HEADER); 480 | for (int i = 0; i <= 5; i++) { 481 | consolePort->print(i); 482 | consolePort->print(". "); 483 | switch (i) { 484 | case 0: 485 | printlnProgMemString(CONSOLE_MENU_CURRENT_CONFIG); 486 | break; 487 | case 1: 488 | printlnProgMemString(CONSOLE_MENU_SPEED); 489 | break; 490 | case 2: 491 | printlnProgMemString(CONSOLE_MENU_PARITY); 492 | break; 493 | case 3: 494 | printlnProgMemString(CONSOLE_MENU_ADDRESS); 495 | break; 496 | case 4: 497 | printlnProgMemString(CONSOLE_MENU_MIRROR); 498 | break; 499 | case 5: 500 | printlnProgMemString(CONSOLE_MENU_SAVE); 501 | break; 502 | } 503 | } 504 | printProgMemString(CONSOLE_MENU_TYPE); 505 | } 506 | 507 | void printConfiguration(byte speed, byte parity, byte address, char *rules) { 508 | char s[] = ": "; 509 | printProgMemString(CONSOLE_MENU_SPEED); 510 | consolePort->print(s); 511 | if (speed == 0) { 512 | consolePort->println(); 513 | } else { 514 | consolePort->println(SPEED_VALUE[speed]); 515 | } 516 | printProgMemString(CONSOLE_MENU_PARITY); 517 | consolePort->print(s); 518 | switch (parity) { 519 | case 1: 520 | consolePort->print("Even"); 521 | break; 522 | case 2: 523 | consolePort->print("Odd"); 524 | break; 525 | case 3: 526 | consolePort->print("None"); 527 | break; 528 | } 529 | consolePort->println(); 530 | printProgMemString(CONSOLE_MENU_ADDRESS); 531 | consolePort->print(s); 532 | if (address == 0) { 533 | consolePort->println(); 534 | } else { 535 | consolePort->println(address); 536 | } 537 | printProgMemString(CONSOLE_MENU_MIRROR); 538 | consolePort->print(s); 539 | if (rules[0] == 0) { 540 | consolePort->println(); 541 | } else { 542 | consolePort->println(rules); 543 | } 544 | } 545 | 546 | boolean rulesEdit(char *buffer, char *value, int c, int size) { 547 | int i; 548 | switch (c) { 549 | case 8: case 127: // backspace 550 | i = strlen(buffer); 551 | if (i > 0) { 552 | consolePort->print('\b'); 553 | consolePort->print(' '); 554 | consolePort->print('\b'); 555 | buffer[i - 1] = 0; 556 | } 557 | break; 558 | case 10: // newline 559 | case 13: // enter 560 | if (strlen(buffer) == size) { 561 | strcpy(value, buffer); 562 | return true; 563 | } else { 564 | return false; 565 | } 566 | break; 567 | default: 568 | if (strlen(buffer) < size) { 569 | if (c >= 'a') { 570 | c -= 32; 571 | } 572 | if (c == 'F' || c == 'I' || c == 'H' || c == 'L' || c == 'T' || c == '-') { 573 | consolePort->print(' '); 574 | consolePort->print('\b'); 575 | consolePort->print((char)c); 576 | strcat_c(buffer, c); 577 | } 578 | } 579 | break; 580 | } 581 | return false; 582 | } 583 | 584 | boolean numberEdit(char *buffer, byte *value, int c, int length, long min, long max) { 585 | int i; 586 | long v; 587 | switch (c) { 588 | case 8: case 127: // backspace 589 | i = strlen(buffer); 590 | if (i > 0) { 591 | consolePort->print('\b'); 592 | consolePort->print(' '); 593 | consolePort->print('\b'); 594 | buffer[i - 1] = 0; 595 | } 596 | break; 597 | case 10: // newline 598 | case 13: // enter 599 | v = strtol(buffer, NULL, 10); 600 | if (v >= min && v <= max) { 601 | *value = (byte)v; 602 | return true; 603 | } else { 604 | return false; 605 | } 606 | break; 607 | default: 608 | if (strlen(buffer) < length) { 609 | if (c >= '0' && c <= '9') { 610 | consolePort->print(' '); 611 | consolePort->print('\b'); 612 | consolePort->print((char)c); 613 | strcat_c(buffer, c); 614 | } 615 | } 616 | break; 617 | } 618 | return false; 619 | } 620 | 621 | void strcat_c(char *s, char c) { 622 | for (; *s; s++); 623 | *s++ = c; 624 | *s++ = 0; 625 | } 626 | --------------------------------------------------------------------------------