commit ec0d7d63de617db476ac994099f9b25ee76ec731 Author: Manuel Weiser Date: Wed Feb 12 21:10:25 2025 +0100 init diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1f50132 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,54 @@ +{ + "files.associations": { + "algorithm": "cpp", + "vector": "cpp", + "cmath": "cpp", + "array": "cpp", + "atomic": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "exception": "cpp", + "functional": "cpp", + "iterator": "cpp", + "map": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "regex": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp" + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..49cde19 --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +# FilaMan - Filament Management System + +A comprehensive filament management system combining ESP32-based hardware for weight measurement and NFC tag reading/writing with a web interface for managing filament spools in conjunction with Bambu Lab AMS and Spoolman. + +## Project Overview + +FilaMan is designed to streamline the management of filament spools for 3D printing. The system consists of an ESP32 microcontroller that handles weight measurement and NFC tag operations, and a web interface that allows users to manage filament spools, monitor AMS (Automatic Material System) status, and interact with Spoolman and Bambu Lab printers. + +### ESP32 Hardware Features +- **Weight Measurement:** Using a load cell with HX711 amplifier for precise weight tracking. +- **NFC Tag Reading/Writing:** PN532 module for reading and writing filament data to NFC tags. +- **OLED Display:** Shows current weight, connection status (WiFi, Bambu Lab, Spoolman). +- **WiFi Connectivity:** WiFiManager for easy network configuration. +- **MQTT Integration:** Connects to Bambu Lab printer for AMS control. +- **Data Persistence:** Stores calibration data in EEPROM. +- **Watchdog Timer:** Ensures system stability. + +### Web Interface Features +- **Real-time Updates:** WebSocket connection for live data updates. +- **NFC Tag Management:** Write filament data to NFC tags. +- **AMS Integration:** + - Display current AMS tray contents. + - Assign filaments to AMS slots. + - Support for external spool holder. +- **Spoolman Integration:** + - List available filament spools. + - Filter and select filaments. + - Update spool weights automatically. + - Track NFC tag assignments. + +## Detailed Functionality + +### ESP32 Functionality +- **Control and Monitor Print Jobs:** The ESP32 communicates with the Bambu Lab printer to control and monitor print jobs. +- **Printer Communication:** Uses MQTT for real-time communication with the printer. +- **User Interactions:** The OLED display provides immediate feedback on the system status, including weight measurements and connection status. + +### Web Interface Functionality +- **User Interactions:** The web interface allows users to interact with the system, select filaments, write NFC tags, and monitor AMS status. +- **UI Elements:** Includes dropdowns for selecting manufacturers and filaments, buttons for writing NFC tags, and real-time status indicators. + +## Installation + +### Prerequisites +- **Software:** + - [PlatformIO](https://platformio.org/) in VS Code + - [Spoolman](https://github.com/Donkie/Spoolman) instance + - Bambu Lab printer (optional for AMS integration) +- **Hardware:** + - ESP32 Development Board + - HX711 Load Cell Amplifier + - Load Cell (weight sensor) + - OLED Display (128x64 SSD1306) + - PN532 NFC Module + - Connecting wires + +### Step-by-Step Installation +1. **Clone the Repository:** + ```bash + git clone https://github.com/yourusername/FilaMan.git + cd FilaMan + ``` +2. **Install Dependencies:** + ```bash + pio lib install + ``` +3. **Flash the ESP32:** + ```bash + pio run --target upload + ``` +4. **Initial Setup:** + - Connect to the "FilaMan" WiFi access point. + - Configure WiFi settings through the captive portal. + - Access the web interface at `http://filaman.local` or the IP address. + +## Hardware Requirements + +### Components +- **ESP32 Development Board:** Any ESP32 variant. +- **HX711 Load Cell Amplifier:** For weight measurement. +- **Load Cell:** Weight sensor. +- **OLED Display:** 128x64 SSD1306. +- **PN532 NFC Module:** For NFC tag operations. +- **Connecting Wires:** For connections. + +### Pin Configuration +| Component | ESP32 Pin | +|-------------------|-----------| +| HX711 DOUT | 16 | +| HX711 SCK | 17 | +| OLED SDA | 21 | +| OLED SCL | 22 | +| PN532 IRQ | 32 | +| PN532 RESET | 33 | + +## Software Dependencies + +### ESP32 Libraries +- `WiFiManager`: Network configuration +- `ESPAsyncWebServer`: Web server functionality +- `ArduinoJson`: JSON parsing and creation +- `PubSubClient`: MQTT communication +- `Adafruit_PN532`: NFC functionality +- `Adafruit_SSD1306`: OLED display control +- `HX711`: Load cell communication + +### External Services +- **Bambu Lab Printer:** For AMS integration. +- **Spoolman:** For filament management. + +## API Communication + +### Spoolman Integration +The system communicates with Spoolman using its REST API for: +- Fetching spool information. +- Updating spool weights. +- Managing NFC tag assignments. + +### Data Format +```json +{ + "version": "2.0", + "protocol": "openspool", + "color_hex": "FFFFFF", + "type": "PLA", + "min_temp": 200, + "max_temp": 220, + "brand": "Vendor", + "sm_id": "1234" +} +``` + +## Documentation + +### Relevant Links +- [PlatformIO Documentation](https://docs.platformio.org/) +- [Spoolman Documentation](https://github.com/Donkie/Spoolman) +- [Bambu Lab Printer Documentation](https://www.bambulab.com/) + +### Tutorials and Examples +- [PlatformIO Getting Started](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) +- [ESP32 Web Server Tutorial](https://randomnerdtutorials.com/esp32-web-server-arduino-ide/) + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + +## Materials + +### Useful Resources +- [ESP32 Official Documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/) +- [Arduino Libraries](https://www.arduino.cc/en/Reference/Libraries) +- [NFC Tag Information](https://learn.adafruit.com/adafruit-pn532-rfid-nfc/overview) + +### Community and Support +- [PlatformIO Community](https://community.platformio.org/) +- [Arduino Forum](https://forum.arduino.cc/) +- [ESP32 Forum](https://www.esp32.com/) + +## Availability + +The code can be tested and the application can be downloaded from the [GitHub repository](https://github.com/yourusername/FilaMan). diff --git a/ca.cert b/ca.cert new file mode 100644 index 0000000..9fcb214 --- /dev/null +++ b/ca.cert @@ -0,0 +1,21 @@ + -----BEGIN CERTIFICATE----- + MIIDZTCCAk2gAwIBAgIUV1FckwXElyek1onFnQ9kL7Bk4N8wDQYJKoZIhvcNAQEL + BQAwQjELMAkGA1UEBhMCQ04xIjAgBgNVBAoMGUJCTCBUZWNobm9sb2dpZXMgQ28u + LCBMdGQxDzANBgNVBAMMBkJCTCBDQTAeFw0yMjA0MDQwMzQyMTFaFw0zMjA0MDEw + MzQyMTFaMEIxCzAJBgNVBAYTAkNOMSIwIAYDVQQKDBlCQkwgVGVjaG5vbG9naWVz + IENvLiwgTHRkMQ8wDQYDVQQDDAZCQkwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB + DwAwggEKAoIBAQDL3pnDdxGOk5Z6vugiT4dpM0ju+3Xatxz09UY7mbj4tkIdby4H + oeEdiYSZjc5LJngJuCHwtEbBJt1BriRdSVrF6M9D2UaBDyamEo0dxwSaVxZiDVWC + eeCPdELpFZdEhSNTaT4O7zgvcnFsfHMa/0vMAkvE7i0qp3mjEzYLfz60axcDoJLk + p7n6xKXI+cJbA4IlToFjpSldPmC+ynOo7YAOsXt7AYKY6Glz0BwUVzSJxU+/+VFy + /QrmYGNwlrQtdREHeRi0SNK32x1+bOndfJP0sojuIrDjKsdCLye5CSZIvqnbowwW + 1jRwZgTBR29Zp2nzCoxJYcU9TSQp/4KZuWNVAgMBAAGjUzBRMB0GA1UdDgQWBBSP + NEJo3GdOj8QinsV8SeWr3US+HjAfBgNVHSMEGDAWgBSPNEJo3GdOj8QinsV8SeWr + 3US+HjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQABlBIT5ZeG + fgcK1LOh1CN9sTzxMCLbtTPFF1NGGA13mApu6j1h5YELbSKcUqfXzMnVeAb06Htu + 3CoCoe+wj7LONTFO++vBm2/if6Jt/DUw1CAEcNyqeh6ES0NX8LJRVSe0qdTxPJuA + BdOoo96iX89rRPoxeed1cpq5hZwbeka3+CJGV76itWp35Up5rmmUqrlyQOr/Wax6 + itosIzG0MfhgUzU51A2P/hSnD3NDMXv+wUY/AvqgIL7u7fbDKnku1GzEKIkfH8hm + Rs6d8SCU89xyrwzQ0PR853irHas3WrHVqab3P+qNwR0YirL0Qk7Xt/q3O1griNg2 + Blbjg3obpHo9 + -----END CERTIFICATE----- \ No newline at end of file diff --git a/display.cpp b/display.cpp new file mode 100644 index 0000000..e69de29 diff --git a/display.h b/display.h new file mode 100644 index 0000000..e69de29 diff --git a/docs/ndef.md b/docs/ndef.md new file mode 100644 index 0000000..a71792d --- /dev/null +++ b/docs/ndef.md @@ -0,0 +1,3255 @@ +# NFC Data Exchange Format (NDEF) + +## Technical Specification + +## NFC Forum + +#### TM + +## NDEF 1. + +## NFCForum-TS-NDEF_1. + +## 2006-07- + + +##### RESTRICTIONS ON USE + +This specification is copyright © 2005-2006 by the NFC Forum, and was made available pursuant to a +license agreement entered into between the recipient (Licensee) and NFC Forum, Inc. (Licensor) and may +be used only by Licensee, and in compliance with the terms of that license agreement (License). If you are +not the Licensee, you are not authorized to make any use of this specification. However, you may obtain a +copy at the following page of Licensor's Website: [http://www.nfc-forum.org/resources/spec_license](http://www.nfc-forum.org/resources/spec_license) after +entering into and agreeing to such license terms as Licensor is then requiring. On the date that this +specification was downloaded by Licensee, those terms were as follows: + +1. LICENSE GRANT. + +Licensor hereby grants Licensee the right, without charge, to copy (for internal purposes only) and share +the Specification with Licensee's members, employees and consultants (as appropriate). This license grant +does not include the right to sublicense, modify or create derivative works based upon the Specification. + +2. NO WARRANTIES. + +THE SPECIFICATION IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, COMPLETENESS AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL LICENSOR, ITS +MEMBERS OR ITS CONTRIBUTORS BE LIABLE FOR ANY CLAIM, OR ANY DIRECT, SPECIAL, +INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING +FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH +THE USE OR PERFORMANCE OF THE SPECIFICATION. + +3. THIRD PARTY RIGHTS. + +Without limiting the generality of Section 2 above, LICENSOR ASSUMES NO RESPONSIBILITY TO +COMPILE, CONFIRM, UPDATE OR MAKE PUBLIC ANY THIRD PARTY ASSERTIONS OF +PATENT OR OTHER INTELLECTUAL PROPERTY RIGHTS THAT MIGHT NOW OR IN THE +FUTURE BE INFRINGED BY AN IMPLEMENTATION OF THE SPECIFICATION IN ITS CURRENT, +OR IN ANY FUTURE FORM. IF ANY SUCH RIGHTS ARE DESCRIBED ON THE SPECIFICATION, +LICENSOR TAKES NO POSITION AS TO THE VALIDITY OR INVALIDITY OF SUCH +ASSERTIONS, OR THAT ALL SUCH ASSERTIONS THAT HAVE OR MAY BE MADE ARE SO +LISTED. + +4. TERMINATION OF LICENSE. + +In the event of a breach of this Agreement by Licensee or any of its employees or members, Licensor shall +give Licensee written notice and an opportunity to cure. If the breach is not cured within thirty (30) days +after written notice, or if the breach is of a nature that cannot be cured, then Licensor may immediately or +thereafter terminate the licenses granted in this Agreement. + +5. MISCELLANEOUS. + +All notices required under this Agreement shall be in writing, and shall be deemed effective five days from +deposit in the mails. Notices and correspondence to the NFC Forum address as it appears below. This +Agreement shall be construed and interpreted under the internal laws of the United States and the +Commonwealth of Massachusetts, without giving effect to its principles of conflict of law. + +NFC Forum, Inc. +401 Edgewater Place, Suite 600 +Wakefield, MA, USA 01880 + + +## Contents + +- 1 Overview........................................................................................................ Contents + - 1.1 Objectives........................................................................................................................... + - 1.1.1 Design Goals......................................................................................................... + - 1.1.2 Anti-Goals............................................................................................................. + - 1.2 References.......................................................................................................................... + - 1.3 Administration.................................................................................................................... + - 1.4 Special Word Usage........................................................................................................... + - 1.5 Name and Logo Usage....................................................................................................... + - 1.6 Intellectual Property........................................................................................................... + - 1.7 Glossary.............................................................................................................................. +- 2 NDEF Mechanisms........................................................................................ + - 2.1 Introduction........................................................................................................................ + - 2.2 Intended Usage................................................................................................................... + - 2.3 NDEF Encapsulation Constructs........................................................................................ + - 2.3.1 Message................................................................................................................. + - 2.3.2 Record................................................................................................................... + - 2.3.3 Record Chunks + - 2.4 NDEF Payload Description................................................................................................ + - 2.4.1 Payload Length...................................................................................................... + - 2.4.2 Payload Type......................................................................................................... + - 2.4.3 Payload Identification.......................................................................................... + - 2.5 NDEF Mechanisms Test Requirements........................................................................... +- 3 The NDEF Specification.............................................................................. + - 3.1 Data Transmission Order.................................................................................................. + - 3.2 Record Layout.................................................................................................................. + - 3.2.1 MB (Message Begin)........................................................................................... + - 3.2.2 ME (Message End).............................................................................................. + - 3.2.3 CF (Chunk Flag).................................................................................................. + - 3.2.4 SR (Short Record)............................................................................................... + - 3.2.5 IL (ID_LENGTH field is present)....................................................................... + - 3.2.6 TNF (Type Name Format).................................................................................. + - 3.2.7 TYPE_LENGTH................................................................................................. + - 3.2.8 ID_LENGTH....................................................................................................... + - 3.2.9 PAYLOAD_LENGTH........................................................................................ + - 3.2.10 TYPE................................................................................................................... + - 3.2.11 ID......................................................................................................................... + - 3.2.12 PAYLOAD.......................................................................................................... + - 3.3 THE NDEF Specification Test Requirements.................................................................. +- 4 Special Considerations............................................................................... + - 4.1 Internationalization........................................................................................................... + - 4.2 Security............................................................................................................................. + - 4.3 Maximum Field Sizes....................................................................................................... + - 4.4 Use of URIs in NDEF...................................................................................................... + - 4.5 Special Consideration Test Requirements........................................................................ +- A. Revision History.......................................................................................... + + +Figures + +## Figures + +#### Figure 1. Example of an NDEF Message with a Set of Records..................................................... 8 + +#### Figure 2. NDEF Octet Ordering.................................................................................................... 13 + +#### Figure 3. NDEF Record Layout.................................................................................................... 14 + +#### Figure 4. NDEF Short-Record Layout (SR=1).............................................................................. 15 + +## Tables + +#### Table 1. TNF Field Values............................................................................................................ 16 + +#### Table 2. Revision History.............................................................................................................. 21 + +## Test Requirements + +#### Test Requirements 1. NDEF Mechanisms Test Requirements..................................................... 11 + +#### Test Requirements 2. The NDEF Specification Test Requirements............................................. 18 + +#### Test Requirements 3. Special Consideration Test Requirements.................................................. 20 + +NFC Data Exchange Format (NDEF) Page ii + + +Overview + +## 1 Overview + +The International Standard ISO/IEC 18092, Near Field Communication – Interface and Protocol +(NFCIP-1), defines an interface and protocol for simple wireless interconnection of closely +coupled devices operating at 13.56 MHz. + +The NFC Data Exchange Format (NDEF) specification defines a message encapsulation format to +exchange information, e.g. between an NFC Forum Device and another NFC Forum Device or an +NFC Forum Tag. + +NDEF is a lightweight, binary message format that can be used to encapsulate one or more +application-defined payloads of arbitrary type and size into a single message construct. Each +payload is described by a type, a length, and an optional identifier. + +Type identifiers may be URIs, MIME media types, or NFC-specific types. This latter format +permits compact identification of well-known types commonly used in NFC Forum applications, +or self-allocation of a name space for organizations that wish to use it for their own NFC-specific +purposes. + +The payload length is an unsigned integer indicating the number of octets in the payload. A +compact, short-record layout is provided for very small payloads. + +The optional payload identifier enables association of multiple payloads and cross-referencing +between them. + +NDEF payloads may include nested NDEF messages or chains of linked chunks of length +unknown at the time the data is generated. + +NDEF is strictly a message format, which provides no concept of a connection or of a logical +circuit, nor does it address head-of-line problems. + +### 1.1 Objectives........................................................................................................................... + +The NFC Data Exchange Format (NDEF) specification is a common data format for NFC Forum +Devices and NFC Forum Tags. + +The NFC Data Exchange Format specification defines the NDEF data structure format as well as +rules to construct a valid NDEF message as an ordered and unbroken collection of NDEF records. +Furthermore, it defines the mechanism for specifying the types of application data encapsulated in +NDEF records. + +The NDEF specification defines only the data structure format to exchange application or service +specific data in an interoperable way, and it does not define any record types in detail—record +types are defined in separate specifications. + +This NDEF specification assumes a reliable underlying protocol and therefore this specification +does not specify the data exchange between two NFC Forum Devices or the data exchange +between an NFC Forum Device and an NFC Forum Tag. Readers are encouraged to review the +NFCIP-1 transport protocol [ISO/IEC 18092]. + +An example of the use of NDEF is when two NFC Forum Devices are in proximity, an NDEF +message is exchanged over the NFC Forum LLCP protocol. When an NFC Forum Device is in +proximity of an NFC Forum Tag, an NDEF message is retrieved from the NFC Forum Tag by +means of the NFC Forum Tag protocols. The data format of the NDEF message is the same in +these two cases so that an NFC Forum Device may process the NDEF information independent of +the type of device or tag with which it is communicating. + +NFC Data Exchange Format (NDEF) Page 1 + + +Overview + +Because of the large number of existing message encapsulation formats, record marking +protocols, and multiplexing protocols, it is best to be explicit about the design goals of NDEF +and, in particular, about what is outside the scope of NDEF. + +#### 1.1.1 Design Goals......................................................................................................... + +The design goal of NDEF is to provide an efficient and simple message format that can +accommodate the following: + +1. Encapsulating arbitrary documents and entities, including encrypted data, XML documents, + XML fragments, image data like GIF and JPEG files, etc. +2. Encapsulating documents and entities initially of unknown size. This capability can be used + to encapsulate dynamically generated content or very large entities as a series of chunks. +3. Aggregating multiple documents and entities that are logically associated in some manner + into a single message. For example, NDEF can be used to encapsulate an NFC-specific + message and a set of attachments of standardized types referenced from that NFC-specific + message. +4. Compact encapsulation of small payloads should be accommodated without introducing + unnecessary complexity to parsers. + +To achieve efficiency and simplicity, the mechanisms provided by this specification have been +deliberately limited to serve these purposes. NDEF has not been designed as a general message +description or document format such as MIME or XML. Instead, NFC applications can take +advantage of such formats by encapsulating them in NDEF messages. + +#### 1.1.2 Anti-Goals............................................................................................................. + +The following list identifies items outside the scope of NDEF: + +1. NDEF does not make any assumptions about the types of payloads that are carried within + NDEF messages or about the message exchange patterns implied by such messages. +2. NDEF does not in any way introduce the notion of a connection or a logical circuit (virtual or + otherwise). +3. NDEF does not attempt to deal with head-of-line blocking problems that might occur when + using stream-oriented protocols like TCP. + +### 1.2 References.......................................................................................................................... + +[ISO/IEC 18092] ISO/IEC 18092, “Information Technology- Telecommunications and +information exchange between systems- Near Field Communication - +Interface and Protocol (NFCIP-1)”. + +[NFC RTD] “NFC Record Type Definition (RTD) Specification”, NFC Forum, 2006. + +[RFC 1700] Reynolds, J. and J. Postel, “Assigned Numbers”, STD 2, RFC 1700, +October 1994. + +[RFC 1900] B. Carpenter, Y. Rekhter, “Renumbering Needs Work”, RFC 1900, IAB, +February 1996. + +[RFC 2046] N. Freed, N. Borenstein, “Multipurpose Internet Mail Extensions +(MIME) Part Two: Media Types” RFC 2046, Innosoft, First Virtual, +November 1996. + +NFC Data Exchange Format (NDEF) Page 2 + + +Overview + +[RFC 2047] K. Moore, “MIME (Multipurpose Internet Mail Extensions) Part Three: +Message Header Extensions for Non-ASCII Text”, RFC 2047, +University of Tennessee, November 1996. + +[RFC 2048] N. Freed, J. Klensin, J. Postel, “Multipurpose Internet Mail Extensions +(MIME) Part Four: Registration Procedures”, RFC 2048, Innosoft, MCI, +ISI, November 1996. + +[RFC 2119] S. Bradner, “Key words for use in RFCs to Indicate Requirement +Levels”, RFC 2119, Harvard University, March 1997. +[http://www.apps.ietf.org/rfc/rfc2119.html](http://www.apps.ietf.org/rfc/rfc2119.html) + +[RFC 2616] R. Fielding, J. Gettys, J. C. Mogul, H. F. Nielsen, T. Berners-Lee, +“Hypertext Transfer Protocol -- HTTP/1.1”, RFC 2616, U.C. Irvine, +DEC W3C/MIT, DEC, W3C/MIT, W3C/MIT, January 1997. + +[RFC 2717] R. Petke, I. King, “Registration Procedures for URL Scheme Names”, +BCP: 35, RFC 2717, UUNET Technologies, Microsoft Corporation, +November 1999. + +[RFC 2718] L. Masinter, H. Alvestrand, D. Zigmond, R. Petke, “Guidelines for new +URL Schemes”, RFC 2718, Xerox Corporation, Maxware, Pirsenteret, +WebTV Networks, Inc., UUNET Technologies, November 1999. + +[RFC 2732] R. Hinden, B. Carpenter, L. Masinter, “Format for Literal IPv +Addresses in URL's”, RFC 2732, Nokia, IBM, AT&T, December 1999. + +[RFC 3023] M. Murata, S. St. Laurent, D. Kohn, “XML Media Types” RFC 3023, +IBM Tokyo Research Laboratory, simonstl.com, Skymoon Ventures, +January 2001. + +[RFC 3986] T. Berners-Lee, R. Fielding, L. Masinter, “Uniform Resource Identifiers +(URI): Generic Syntax”, RFC 3986, MIT/LCS, U.C. Irvine, Xerox +Corporation, January 2005. [http://www.apps.ietf.org/rfc/rfc3986.html](http://www.apps.ietf.org/rfc/rfc3986.html) + +[URI SCHEME] List of Uniform Resource Identifier (URI) schemes registered by IANA +is available at:http://www.iana.org/assignments/uri-schemes + +NFC Data Exchange Format (NDEF) Page 3 + + +Overview + +### 1.3 Administration.................................................................................................................... + +The NFC Forum Data Exchange Format Specification is an open specification supported by the +Near Field Communication Forum, Inc., located at: + +``` +401 Edgewater Place, Suite 600 +Wakefield, MA, 01880 +Tel.: +1 781-876- +Fax: +1 781-224- +http://www.nfc-forum.org/ +``` +The Devices technical working group maintains this specification. + +### 1.4 Special Word Usage........................................................................................................... + +The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, +“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this +document are to be interpreted as described in RFC 2119. + +### 1.5 Name and Logo Usage....................................................................................................... + +The Near Field Communication Forum’s policy regarding the use of the trademarks NFC Forum +and the NFC Forum logo is as follows: + +- Any company MAY claim compatibility with NFC Forum specifications, whether a member + of the NFC Forum or not. +- Permission to use the NFC Forum logos is automatically granted to designated members only + as stipulated on the most recent Membership Privileges document, during the period of time + for which their membership dues are paid. +- Member’s distributors and sales representatives MAY use the NFC Forum logo in promoting + member’s products sold under the name of the member. +- The logo SHALL be printed in black or in color as illustrated on the Logo Page that is + available from the NFC Forum at the address above. The aspect ratio of the logo SHALL be + maintained, but the size MAY be varied. Nothing MAY be added to or deleted from the + logos. +- Since the NFC Forum name is a trademark of the Near Field Communication Forum, the + following statement SHALL be included in all published literature and advertising material in + which the name or logo appears: + NFC Forum and the NFC Forum logo are trademarks of the Near Field Communication + Forum. + +### 1.6 Intellectual Property........................................................................................................... + +The NFC Data Exchange Format (NDEF) Specification conforms to the Intellectual Property +guidelines specified in the NFC Forum's Intellectual Property Right Policy, as approved on +November 9, 2004 and outlined in the NFC Forum Rules of Procedures, as approved on +December 17, 2004. + +NFC Data Exchange Format (NDEF) Page 4 + + +Overview + +### 1.7 Glossary.............................................................................................................................. + +NDEF application + +``` +The logical, higher-layer application on an NFC Forum Device using NDEF to format +information for exchange with other NFC Forum Devices or NFC Forum Tags. Also user +application or NDEF user application. +``` +NDEF message + +``` +The basic message construct defined by this specification. An NDEF message contains +one or more NDEF records (see section 2.3.1). +``` +NDEF record + +``` +An NDEF record contains a payload described by a type, a length, and an optional +identifier (see section 2.3.2). +``` +NDEF short record + +``` +An NDEF record with the SR flag set to 1; the PAYLOAD_LENGTH field in short +records is a single octet allowing payloads or chunks of up to 255 bytes to be carried (see +section 3.2.4). +``` +NDEF record chunk + +``` +An NDEF record that contains a chunk of a payload rather than a full payload (see +section 2.3.3). Each record chunk carrying a portion of the chunked payload, except the +last record of each chunked payload, has its CF flag set to 1. +``` +NDEF payload + +``` +The application data carried within an NDEF record. +``` +NDEF chunked payload + +``` +Application data that has been partitioned into multiple chunks each carried in a separate +NDEF record, where each of these records except the last has the CF flag set to 1. This +facility can be used to carry dynamically generated content for which the payload size is +not known in advance or very large entities that don't fit into a single NDEF record. +Chunked payloads are not intended to support multiplexing or streaming of content and +such use is deprecated. (See section 2.3.3.) +``` +NDEF payload length + +``` +The size of the payload in a single NDEF record indicated as the number of octets (see +section 2.4.1). +``` +NDEF payload type + +``` +An identifier that indicates the type of the payload. This specification supports URIs +[RFC 3986], MIME media type constructs [RFC 2616], as well as an NFC-specific +record type as type identifiers (see section 2.4.2). +``` +NDEF payload identifier + +``` +An optional URI that can be used to identify a payload (see section 2.4.3). +``` +NDEF generator + +``` +An entity or module that encapsulates application-defined payloads within NDEF +messages. +``` +NFC Data Exchange Format (NDEF) Page 5 + + +Overview + +NDEF parser + +``` +An entity or module that parses NDEF messages and hands off the payloads to an NDEF +application. +``` +User Application + +``` +See NDEF Application. +``` +NFC Data Exchange Format (NDEF) Page 6 + + +NDEF Mechanisms + +## 2 NDEF Mechanisms........................................................................................ + +This section describes the mechanisms used in NDEF. The specific syntax for these mechanisms +is defined in Section 3. + +### 2.1 Introduction........................................................................................................................ + +NFC Forum Data Exchange Format is a lightweight binary message format designed to +encapsulate one or more application-defined payloads into a single message construct. An NDEF +message contains one or more NDEF records, each carrying a payload of arbitrary type and up to +232 -1 octets in size. Records can be chained together to support larger payloads. An NDEF record +carries three parameters for describing its payload: the payload length, the payload type, and an +optional payload identifier. The purpose of these parameters is as follows: + +The payload length + +``` +The payload length indicates the number of octets in the payload (see section 2.4.1). By +providing the payload length within the first 8 octets of a record, efficient record boundary +detection is possible. +``` +The payload type + +``` +The NDEF payload type identifier indicates the type of the payload. NDEF supports URIs +[RFC 3986], MIME media type constructs [RFC 2046], and an NFC-specific type format as +type identifiers (see section 2.4.2). By indicating the type of a payload, it is possible to +dispatch the payload to the appropriate user application. +``` +The payload identifier + +``` +A payload may be given an optional identifier in the form of an absolute or relative URI (see +section 2.4.3). The use of an identifier enables payloads that support URI linking +technologies to cross-reference other payloads. +``` +### 2.2 Intended Usage................................................................................................................... + +The intended usage of NDEF is as follows: A user application wants to encapsulate one or more +related documents into a single NDEF message. For example, this can be an application-specific +message along with a set of attachments, each of standardized type. The NDEF generator +encapsulates each document in NDEF records as payload or chunked payload, indicating the type +and length of the payload along with an optional identifier. The NDEF records are then put +together to form a single NDEF message. The NDEF message is transmitted across an NFC link +to another NFC Forum Device where they are received and parsed, or as an intermediate step, the +message is written to an NFC Forum Tag. An NFC Forum Device brought close to this NFC +Forum Tag will read the NDEF message from this tag and hand it over to the NDEF parser. The +NDEF parser deconstructs the NDEF message and hands the payloads to a (potentially different) +user application. Each NDEF message MUST be sent or received in its entirety. + +NDEF records can encapsulate documents of any type. It is possible to carry MIME messages in +NDEF records by using a media type such as “message/rfc822”. An NDEF message can be +encapsulated in an NDEF record by using an NFC-specific predefined type (see [NFC RTD]). + +It is important to note that although MIME entities are supported, there are no assumptions in +NDEF that a record payload is MIME; NDEF makes no assumption concerning the types of the +payloads carried in an NDEF message. Said differently, an NDEF parser need not inspect the +NDEF record type nor peer inside an NDEF record in order to parse the NDEF message. + +NFC Data Exchange Format (NDEF) Page 7 + + +NDEF Mechanisms + +NDEF provides no support for error handling. It is up to the NDEF parser to determine the +implications of receiving a malformed NDEF message or an NDEF message containing a field +length beyond its processing capabilities. It is the responsibility of the user applications involved +to provide any additional functionality such as QoS that they may need as part of the overall +system in which they participate. + +### 2.3 NDEF Encapsulation Constructs........................................................................................ + +#### 2.3.1 Message................................................................................................................. + +An NDEF message is composed of one or more NDEF records. The first record in a message is +marked with the MB (Message Begin) flag set and the last record in the message is marked with +the ME (Message End) flag set (see sections 3.2.1 and 3.2.2). The minimum message length is +one record which is achieved by setting both the MB and the ME flag in the same record. Note +that at least two record chunks are required in order to encode a chunked payload (see section +2.3.3). The maximum number of NDEF records that can be carried in an NDEF message is +unbounded. + +NDEF messages MUST NOT overlap; that is, the MB and the ME flags MUST NOT be used to +nest NDEF messages. NDEF messages MAY be nested by carrying a full NDEF message as a +payload within an NDEF record. + +``` +NDEF Message +R 1 MB=1 ... Rr ... Rs ... Rt ME= +``` +``` +Figure 1. Example of an NDEF Message with a Set of Records +``` +The message head is to the left and the tail to the right, with the logical record indices t > s > r > + +1. The MB (Message Begin) flag is set in the first record (index 1) and the ME (Message End) +flag is set in the last record (index t). + +Actual NDEF records do not carry an index number; the ordering is implicitly given by the order +in which the records are serialized. For example, if records are repackaged by an intermediate +application, then that application is responsible for ensuring that the order of records is preserved. + +#### 2.3.2 Record................................................................................................................... + +A record is the unit for carrying a payload within an NDEF message. Each payload is described +by its own set of parameters (see section 2.4). + +#### 2.3.3 Record Chunks + +A record chunk carries a chunk of a payload. Chunked payloads can be used to partition +dynamically generated content or very large entities into multiple subsequent record chunks +serialized within the same NDEF message. + +Chunking is not a mechanism for introducing multiplexing or data streaming into NDEF and it +MUST NOT be used for those purposes. It is a mechanism to reduce the need for outbound +buffering on the generating side. This is similar to the message chunking mechanism defined in +HTTP/1.1 [RFC 2616]. + +An NDEF message can contain zero or more chunked payloads. Each chunked payload is +encoded as an initial record chunk followed by zero or more middle record chunks and finally by + +NFC Data Exchange Format (NDEF) Page 8 + + +NDEF Mechanisms + +a terminating record chunk. Each record chunk is encoded as an NDEF record using the +following encoding rules: + +- The initial record chunk is an NDEF record with the CF (Chunk Flag) flag set (see section + 3.2.3). The type of the entire chunked payload MUST be indicated in the TYPE field + regardless of whether the PAYLOAD_LENGTH field value is zero or not. The ID field MAY + be used to carry an identifier of the entire chunked payload. The PAYLOAD_LENGTH field + of this initial record indicates the size of the data carried in the PAYLOAD field of the initial + record only, not the entire payload size (see section 2.4.1). +- Each middle record chunk is an NDEF record with the CF flag set indicating that this record + chunk contains the next chunk of data of the same type and with the same identifier as the + initial record chunk. The value of the TYPE_LENGTH and the IL fields MUST be zero and + the TNF (Type Name Format) field value MUST be 0x06 (Unchanged) (see section 3.2.6). + The PAYLOAD_LENGTH field indicates the size of the data carried in the PAYLOAD field + of this single middle record only (see section 2.4.1). +- The terminating record chunk is an NDEF record with the CF flag cleared, indicating that + this record chunk contains the last chunk of data of the same type and with the same identifier + as the initial record chunk. As with the middle record chunks, the value of the + TYPE_LENGTH and the IL fields MUST be zero and the TNF (Type Name Format) field + value MUST be 0x06 (Unchanged) (see section 3.2.6). The PAYLOAD_LENGTH field + indicates the size of the data carried in the PAYLOAD field of this terminating record chunk + (see section 2.4.1). + +A chunked payload MUST be entirely encapsulated within a single NDEF message. That is, a +chunked payload MUST NOT span multiple NDEF messages. As a consequence, neither an +initial nor a middle record chunk can have the ME (Message End) flag set. + +### 2.4 NDEF Payload Description................................................................................................ + +Each record contains information about the payload carried within it. This section introduces the +mechanisms by which these payloads are described. + +#### 2.4.1 Payload Length...................................................................................................... + +Regardless of the relationship of a record to other records, the payload length always indicates the +length of the payload encapsulated in this record. The length of the payload is indicated in the +PAYLOAD_LENGTH field. The PAYLOAD_LENGTH field is one octet for short records and +four octets for normal records. Short records are indicated by setting the SR bit flag to a value of 1 +(see section 3.2.4). Zero is a valid payload length. + +#### 2.4.2 Payload Type......................................................................................................... + +The payload type of a record indicates the kind of data being carried in the payload of that record. +This may be used to guide the processing of the payload at the discretion of the user application. +The type of the first record, by convention, SHOULD provide the processing context not only for +the first record but for the whole NDEF message. Additional context for processing the message +MAY be provided, for example, by the link layer service access point (LSAP) or transport service +port (e.g. TCP, UDP, etc) at which the message was received and by other communication +parameters. + +It is important to emphasize that NDEF mandates no specific processing model for NDEF +messages. The usage of the payload types is entirely at the discretion of the user application. The + +NFC Data Exchange Format (NDEF) Page 9 + + +NDEF Mechanisms + +comments regarding usage above should be taken as guidelines for building processing +conventions, including mappings of higher level application semantics onto NDEF. + +The format of the TYPE field value is indicated using the TNF (Type Name Format) field (see +section 3.2.6). This specification supports TYPE field values in the form of NFC Forum well- +known types, NFC Forum external types, absolute URIs [RFC 3986], and MIME media-type +constructs. The first allows for NFC Forum specified payload types supporting NFC Forum +reference applications [NFC RTD]; URIs provide for decentralized control of the value space; +media types allow NDEF to take advantage of the media type value space maintained by IANA +[RFC 1700]. + +The media type registration process is outlined in RFC 2048 [RFC 2048]. Use of non-registered +media types is discouraged. The URI scheme registration process is described in RFC 2717 [RFC +2717]. It is RECOMMENDED that only well-known URI schemes registered by IANA be used +(see [URI SCHEME] for a current list). + +URIs can be used for message types that are defined by URIs. Records that carry a payload with +an XML-based message type MAY use the XML namespace identifier of the root element as the +TYPE field value. A SOAP/1.1 message, for example, may be represented by the URI + +``` +http://schemas.xmlsoap.org/soap/envelope/ +``` +NOTE: Encoding of URI characters which fall outside the US-ASCII range is left to the NDEF +application. Therefore, an NDEF parser must not assume any particular encoding for this field. +See [RFC 3986] and the specifications of particular protocol schemes (e.g. HTTP, URN, etc.) for +more information on parsing of URIs and character encoding requirements for non-ASCII +characters. + +Records that carry a payload with an existing, registered media type SHOULD carry a TYPE +field value of that media type. The TYPE field indicates the type of the payload; it does NOT +refer to a MIME message that contains an entity of the given type. For example, the media type + +``` +image/jpeg +``` +indicates that the payload is an image in JPEG format using JFIF encoding as defined by RFC +2046 [RFC 2046]. Similarly, the media type + +``` +message/http +``` +indicates that the payload is an HTTP message as defined by RFC 2616 [RFC 2616]. The value + +``` +application/xml; charset=“utf-16” +``` +indicates that the payload is an XML document as defined by RFC 3023 [RFC3023]. + +#### 2.4.3 Payload Identification.......................................................................................... + +The optional payload identifier allows user applications to identify the payload carried within an +NDEF record. By providing a payload identifier, it becomes possible for other payloads +supporting URI-based linking technologies to refer to that payload. NDEF does not mandate any +particular linking mechanism or format but leaves this to the user application to define in the +language it prefers. + +It is important that payload identifiers are maintained so that references to those payloads are not +broken. If records are repackaged, for example, by an intermediate application, then that +application is responsible for ensuring that the linked relationship between identified payloads is +preserved. + +NFC Data Exchange Format (NDEF) Page 10 + + +NDEF Mechanisms + +### 2.5 NDEF Mechanisms Test Requirements........................................................................... + +This section identifies the testable requirements of the NDEF mechanisms defined in chapter 2. +The purpose of this section and the table below is to guide the development of conformance tests +and does not supersede the normative requirements presented in the other sections of this chapter. + +``` +Test Requirements 1. NDEF Mechanisms Test Requirements +``` +Message requirements + +Each NDEF message MUST be exchanged in its entirety. + +The first record in a message is marked with the MB (Message Begin) flag set. + +The last record in the message is marked with the ME (Message End) flag set. + +NDEF messages MUST NOT overlap; that is, the MB and the ME flags MUST NOT be used to +nest NDEF messages. + +Record chunk requirements + +Each chunked payload is encoded as an initial record chunk followed by 0 or more middle record +chunks and finally by a terminating record chunk. + +The initial record chunk is an NDEF record with the CF (Chunk Flag) flag set. + +The type of the entire chunked payload MUST be indicated in the TYPE field of the initial record +chunk. + +The PAYLOAD_LENGTH field of the initial record indicates the size of the data carried in the +PAYLOAD field of the initial record only, not the entire payload size. + +Each middle record chunk is an NDEF record with the CF flag set. + +For each middle record chunk the value of the TYPE_LENGTH and the IL fields MUST be 0. + +For each middle record chunk the TNF (Type Name Format) field value MUST be 0x +(Unchanged). + +For each middle record chunk, the PAYLOAD_LENGTH field indicates the size of the data +carried in the PAYLOAD field of this single record only. + +The terminating record chunk is an NDEF record with the CF flag cleared. + +For the terminating record chunk, the value of the TYPE_LENGTH and the IL fields MUST be 0. + +For the terminating record chunk, the TNF (Type Name Format) field value MUST be 0x +(Unchanged). + +For the terminating record chunk, the PAYLOAD_LENGTH field indicates the size of the data +carried in the PAYLOAD field of this record only. + +A chunked payload MUST be entirely encapsulated within a single NDEF message. + +An initial record chunk MUST NOT have the ME (Message End) flag set. + +A middle record chunk MUST NOT have the ME (Message End) flag set. + +NFC Data Exchange Format (NDEF) Page 11 + + +NDEF Mechanisms + +NDEF payload requirements + +The PAYLOAD_LENGTH field is four octets for normal records. + +The PAYLOAD_LENGTH field is one octet for records with an SR (Short Record) bit flag value +of 1. + +The PAYLOAD_LENGTH field of a short record MUST have a value between 0 and 255. + +The PAYLOAD_LENGTH field of a normal record MUST have a value between 0 and 2^32 -1. + +NFC Data Exchange Format (NDEF) Page 12 + + +The NDEF Specification + +## 3 The NDEF Specification.............................................................................. + +### 3.1 Data Transmission Order.................................................................................................. + +The order of transmission of the NDEF record described in this document is resolved to the octet +level. For diagrams showing a group of octets, the order of transmission of those octets is first left +to right and then top to bottom, as they are read in English. For example, in the diagram in Figure +2, the octets are transmitted in the order they are numbered. + ++--+--+--+--+--+--+--+--+ +| Octet 1 | ++--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ +| Octet 2 | Octet 3 | ++--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ +| Octet 4 | ++--+--+--+--+--+--+--+--+ +| Octet 5 | ++--+--+--+--+--+--+--+--+ + +``` +Figure 2. NDEF Octet Ordering +``` +Whenever an octet represents a numeric quantity, the leftmost bit in the diagram is the high order +or most significant bit. For each multi-octet field representing a numeric quantity defined by +NDEF, the leftmost bit of the whole field is the most significant bit. Such quantities are +transmitted in a big-endian manner with the most significant octet transmitted first. + +NFC Data Exchange Format (NDEF) Page 13 + + +The NDEF Specification + +### 3.2 Record Layout.................................................................................................................. + +NDEF records are variable length records with a common format illustrated in the figure below. +In the following sections, the individual record fields are described in more detail. + +``` +7 6 5 4 3 2 1 0 +``` +``` +MB ME CF SR IL TNF +``` +``` +TYPE LENGTH +``` +``` +PAYLOAD LENGTH 3 +``` +``` +PAYLOAD LENGTH 2 +``` +``` +PAYLOAD LENGTH 1 +``` +``` +PAYLOAD LENGTH 0 +``` +``` +ID LENGTH +``` +``` +TYPE +``` +``` +ID +``` +``` +PAYLOAD +``` +``` +Figure 3. NDEF Record Layout +``` +#### 3.2.1 MB (Message Begin)........................................................................................... + +The MB flag is a 1-bit field that when set indicates the start of an NDEF message (see section +2.3.1). + +#### 3.2.2 ME (Message End).............................................................................................. + +The ME flag is a 1-bit field that when set indicates the end of an NDEF message (see section +2.3.1). Note, that in case of a chunked payload, the ME flag is set only in the terminating record +chunk of that chunked payload (see section 2.3.3). + +#### 3.2.3 CF (Chunk Flag).................................................................................................. + +The CF flag is a 1-bit field indicating that this is either the first record chunk or a middle record +chunk of a chunked payload (see section 2.3.3 for a description of how to encode a chunked +payload). + +NFC Data Exchange Format (NDEF) Page 14 + + +The NDEF Specification + +#### 3.2.4 SR (Short Record)............................................................................................... + +The SR flag is a 1-bit field indicating, if set, that the PAYLOAD_LENGTH field is a single octet. +This short record layout is intended for compact encapsulation of small payloads which will fit +within PAYLOAD fields of size ranging between 0 to 255 octets. + +``` +7 6 5 4 3 2 1 0 +``` +``` +MB ME CF 1 IL TNF +``` +``` +TYPE LENGTH +``` +``` +PAYLOAD LENGTH +``` +``` +ID LENGTH +``` +``` +TYPE +``` +``` +ID +``` +``` +PAYLOAD +``` +``` +Figure 4. NDEF Short-Record Layout (SR=1) +``` +While it is tempting for implementers to choose one or the other record layout exclusively for a +given application, NDEF parsers MUST accept both normal and short record layouts. NDEF +generators MAY generate either record layout as they deem appropriate. A single NDEF message +MAY contain both normal and short records. + +#### 3.2.5 IL (ID_LENGTH field is present)....................................................................... + +The IL flag is a 1-bit field indicating, if set, that the ID_LENGTH field is present in the header as +a single octet. If the IL flag is zero, the ID_LENGTH field is omitted from the record header and +the ID field is also omitted from the record. + +NFC Data Exchange Format (NDEF) Page 15 + + +The NDEF Specification + +#### 3.2.6 TNF (Type Name Format).................................................................................. + +The TNF field value indicates the structure of the value of the TYPE field (see section 2.4.2 for a +description of the TYPE field and section 4 for a description of internationalization issues related +to the TYPE field). The TNF field is a 3-bit field with values defined in the table below: + +``` +Table 1. TNF Field Values +``` +``` +Type Name Format Value +``` +``` +Empty 0x +NFC Forum well-known type [NFC RTD] 0x +``` +``` +Media-type as defined in RFC 2046 [RFC 2046] 0x +Absolute URI as defined in RFC 3986 [RFC 3986] 0x +NFC Forum external type [NFC RTD] 0x +``` +``` +Unknown 0x +Unchanged (see section 2.3.3) 0x +``` +``` +Reserved 0x +``` +The value 0x00 (Empty) indicates that there is no type or payload associated with this record. +When used, the TYPE_LENGTH, ID_LENGTH, and PAYLOAD_LENGTH fields MUST be +zero and the TYPE, ID, and PAYLOAD fields are thus omitted from the record. This TNF value +can be used whenever an empty record is needed; for example, to terminate an NDEF message in +cases where there is no payload defined by the user application. + +The value 0x01 (NFC Forum well-known type) indicates that the TYPE field contains a value that +follows the RTD type name format defined in the NFC Forum RTD specification [NFC RTD]. + +The value 0x02 (media-type) indicates that the TYPE field contains a value that follows the +media-type BNF construct defined by RFC 2046 [RFC 2046]. + +The value 0x03 (absolute-URI) indicates that the TYPE field contains a value that follows the +absolute-URI BNF construct defined by RFC 3986 [RFC 3986]. + +The value 0x04 (NFC Forum external type) indicates that the TYPE field contains a value that +follows the type name format defined in [NFC RTD] for external type names. + +The value 0x05 (Unknown) SHOULD be used to indicate that the type of the payload is +unknown. This is similar to the “application/octet-stream” media type defined by MIME [RFC +2046]. When used, the TYPE_LENGTH field MUST be zero and thus the TYPE field is omitted +from the NDEF record. Regarding implementation, it is RECOMMENDED that an NDEF parser +receiving an NDEF record of this type, without further context to its use, provides a mechanism +for storing but not processing the payload (see section 4.2). + +The value 0x06 (Unchanged) MUST be used in all middle record chunks and the terminating +record chunk used in chunked payloads (see section 2.3.3). It MUST NOT be used in any other +record. When used, the TYPE_LENGTH field MUST be zero and thus the TYPE field is omitted +from the NDEF record. + +NFC Data Exchange Format (NDEF) Page 16 + + +The NDEF Specification + +There is no default value for the TNF field. Reserved (or unassigned) TNF field values are for +future use and MUST NOT be used. An NDEF parser that receives an NDEF record with an +unknown or unsupported TNF field value SHOULD treat it as 0x05 (Unknown). + +#### 3.2.7 TYPE_LENGTH................................................................................................. + +The TYPE_LENGTH field is an unsigned 8-bit integer that specifies the length in octets of the +TYPE field. The TYPE_LENGTH field is always zero for certain values of the TNF field (see +section 3.2.6). + +#### 3.2.8 ID_LENGTH....................................................................................................... + +The ID_LENGTH field is an unsigned 8-bit integer that specifies the length in octets of the ID +field. This field is present only if the IL flag is set to 1 in the record header. An ID_LENGTH of +zero octets is allowed and, in such cases, the ID field is omitted from the NDEF record. + +#### 3.2.9 PAYLOAD_LENGTH........................................................................................ + +The PAYLOAD_LENGTH field is an unsigned integer that specifies the length in octets of the +PAYLOAD field (the application payload). The size of the PAYLOAD_LENGTH field is +determined by the value of the SR flag (see section 3.2.4). + +If the SR flag is set, the PAYLOAD_LENGTH field is a single octet representing an 8-bit +unsigned integer. + +If the SR flag is clear, the PAYLOAD_LENGTH field is four octets representing a 32-bit +unsigned integer. Transmission order of the octets is MSB-first (see section 3.1). + +A payload length of 0 is allowed in which case the PAYLOAD field is omitted from the NDEF +record. Application payloads larger than 2^32 -1 octets can be accommodated by using chunked +payloads (see section 2.3.3). + +#### 3.2.10 TYPE................................................................................................................... + +The value of the TYPE field is an identifier describing the type of the payload (see section 2.4.2). +The value of the TYPE field MUST follow the structure, encoding, and format implied by the +value of the TNF field (see section 3.2.6). + +An NDEF parser receiving an NDEF record with a TNF field value that it supports but an +unknown TYPE field value SHOULD interpret the type identifier of that record as if the TNF +field value were 0x05 (Unknown). + +It is STRONGLY RECOMMENDED that the type identifier be globally unique and maintained +with stable and well-defined semantics over time. + +#### 3.2.11 ID......................................................................................................................... + +The value of the ID field is an identifier in the form of a URI reference [RFC 3986] (see sections +2.4.3 and 4.4). The required uniqueness of the message identifier is guaranteed by the generator. +The URI reference can be either relative or absolute; NDEF does not define a base URI which +means that user applications using relative URIs MUST provide an actual or a virtual base URI +(see [RFC 3986]). + +Middle and terminating record chunks (that is, records containing other than the initial chunk of a +chunked payload; see section 2.3.3) MUST NOT have an ID field. All other records MAY have +an ID field. + +NFC Data Exchange Format (NDEF) Page 17 + + +The NDEF Specification + +#### 3.2.12 PAYLOAD.......................................................................................................... + +The PAYLOAD field carries the payload intended for the NDEF user application. Any internal +structure of the data carried within the PAYLOAD field is opaque to NDEF. + +### 3.3 THE NDEF Specification Test Requirements.................................................................. + +This section identifies the testable requirements of the NDEF mechanisms defined in chapter 3. +The purpose of this section is to guide the development of conformance tests and does not +supersede the normative requirements presented in the other sections of this chapter. + +``` +Test Requirements 2. The NDEF Specification Test Requirements +``` +Data transmission order requirements + +Quantities are transmitted in a big-endian manner with the most significant octet transmitted +first. + +Record layout requirements + +NDEF parsers MUST accept both normal and short record layouts. + +NDEF parsers MUST accept single NDEF messages composed of both normal and short +records. + +If the IL flag is 1, the ID_LENGTH field MUST be present. + +If the IL flag is 0, the ID_LENGTH field MUST NOT be present. + +If the IL flag is 0, the ID field MUST NOT be present. + +The TNF field MUST have a value between 0x00 and 0x06. + +If the TNF value is 0x00, the TYPE_LENGTH, ID_LENGTH, and PAYLOAD_LENGTH +fields MUST be zero and the TYPE, ID, and PAYLOAD fields MUST be omitted from the +record. + +If the TNF value is 0x05 (Unknown), the TYPE_LENGTH field MUST be 0 and the TYPE +field MUST be omitted from the NDEF record. + +If the TNF value is 0x06 (Unchanged), the TYPE_LENGTH field MUST be 0 and the TYPE +field MUST be omitted from the NDEF record. + +The TNF value MUST NOT be 0x07. + +If the ID_LENGTH field has a value 0, the ID field MUST NOT be present. + +If the SR flag is 0, the PAYLOAD_LENGTH field is four octets, representing a 32-bit +unsigned integer, and the transmission order of the octets is MSB-first. + +If the SR flag is 1, the PAYLOAD_LENGTH field is a single octet representing an 8-bit +unsigned integer. + +If the PAYLOAD_LENGTH field value is 0, the PAYLOAD field MUST NOT be present. + +The value of the TYPE field MUST follow the structure, encoding, and format implied by the +value of the TNF field. + +Middle and terminating record chunks MUST NOT have an ID field. + +NFC Data Exchange Format (NDEF) Page 18 + + +Special Considerations + +## 4 Special Considerations............................................................................... + +### 4.1 Internationalization........................................................................................................... + +Identifiers used in NDEF such as URIs and MIME media-type constructs may provide different +levels of support for internationalization. Implementers are referred to RFC 2718 [RFC 2718] for +internationalization considerations of URIs, RFC 2046 [RFC 2046] for internationalization +considerations of MIME media types and RFC 2047 [RFC 2047] for internationalization of +message headers (MIME). + +### 4.2 Security............................................................................................................................. + +Implementers should pay special attention to the security implications of any record types that +can cause the remote execution of any actions in the recipient’s environment. Before accepting +records of any type, an application should be aware of the particular security implications +associated with that type. + +Security considerations for media types in general are discussed in RFC 2048 [RFC 2048] and in +the context of the “application” media types in RFC 2046 [RFC 2046]. + +### 4.3 Maximum Field Sizes....................................................................................................... + +The size of the PAYLOAD field and the values used in the ID field and the TYPE field are +limited by the maximum size of these fields. The maximum size of the PAYLOAD field is 2^32 -1 +octets in the normal NDEF record layout and 255 octets in the short record layout (see section +3.2.4). The maximum size of values in the ID and TYPE fields is 255 octets in both record +layouts. + +While messages formed to these maximal record and field sizes are considered well-formed, not +all user applications will have the ability or the need to handle payload content, payload IDs, or +types identifiers of these maximal sizes. NDEF parsers that are resource-constrained MAY +choose to reject messages that are not sized to fit their specific needs. + +However, NDEF parsers MUST NOT reject an NDEF message based solely on the value of the +SR flag. + +### 4.4 Use of URIs in NDEF...................................................................................................... + +NDEF uses URIs [RFC 3986] for some identifiers. To NDEF, a URI is simply a formatted string +that identifies—via name, location, or any other characteristic—a resource. + +The use of IP addresses in URIs SHOULD be avoided whenever possible (see RFC 1900 [RFC +1900]). However, when used, the literal format for IPv6 addresses in URIs as described by RFC +2732 [RFC 2732] SHOULD be supported. + +NDEF does not define any equivalence rules for URIs in general as these are defined by the +individual URI schemes and by RFC 3986 [RFC 3986]. However, because of inconsistencies +with respect to some URI equivalence rules in many current URI parsers, it is RECOMMENDED +that generators of NDEF messages rely only on the most rudimentary equivalence rules defined +by RFC 3986. + +NFC Data Exchange Format (NDEF) Page 19 + + +Special Considerations + +### 4.5 Special Consideration Test Requirements........................................................................ + +This section identifies the testable requirements of the NDEF mechanisms defined in chapter 4. +The purpose of this section and the table below is to guide the development of conformance tests +and does not supersede the normative requirements presented in the other sections of this chapter. + +``` +Test Requirements 3. Special Consideration Test Requirements +``` +An NDEF parser MUST NOT reject an NDEF message based solely on the value of the SR +flag. + +An NDEF parser MAY reject messages that include records with TYPE, ID, or PAYLOAD +fields larger than its design limits. + +NFC Data Exchange Format (NDEF) Page 20 + + +Revision History + +## A. Revision History.......................................................................................... + +The following table outlines the revision history of the NDEF Technical Specification. + +``` +Table 2. Revision History +``` +Document Name Revision and +Release Date + +``` +Status Change notice Supersedes +``` +NFCForum-TS- +NDEF_1.0 + +``` +1.0, July 2006 Final none +``` +NFC Data Exchange Format (NDEF) Page 21 + + +# NFC Record Type Definition (RTD) + +## Technical Specification + +## NFC Forum + +#### TM + +## RTD 1.0 + +## NFCForum-TS-RTD_1.0 + +## 2006-07-24 + + +##### RESTRICTIONS ON USE + +This specification is copyright © 2005-2006 by the NFC Forum, and was made available pursuant to a +license agreement entered into between the recipient (Licensee) and NFC Forum, Inc. (Licensor) and may +be used only by Licensee, and in compliance with the terms of that license agreement (License). If you are +not the Licensee, you are not authorized to make any use of this specification. However, you may obtain a +copy at the following page of Licensor's Website: [http://www.nfc-forum.org/resources/spec_license](http://www.nfc-forum.org/resources/spec_license) after +entering into and agreeing to such license terms as Licensor is then requiring. On the date that this +specification was downloaded by Licensee, those terms were as follows: + +1. LICENSE GRANT. + +Licensor hereby grants Licensee the right, without charge, to copy (for internal purposes only) and share +the Specification with Licensee's members, employees and consultants (as appropriate). This license grant +does not include the right to sublicense, modify or create derivative works based upon the Specification. + +2. NO WARRANTIES. + +THE SPECIFICATION IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, COMPLETENESS AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL LICENSOR, ITS +MEMBERS OR ITS CONTRIBUTORS BE LIABLE FOR ANY CLAIM, OR ANY DIRECT, SPECIAL, +INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING +FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH +THE USE OR PERFORMANCE OF THE SPECIFICATION. + +3. THIRD PARTY RIGHTS. + +Without limiting the generality of Section 2 above, LICENSOR ASSUMES NO RESPONSIBILITY TO +COMPILE, CONFIRM, UPDATE OR MAKE PUBLIC ANY THIRD PARTY ASSERTIONS OF +PATENT OR OTHER INTELLECTUAL PROPERTY RIGHTS THAT MIGHT NOW OR IN THE +FUTURE BE INFRINGED BY AN IMPLEMENTATION OF THE SPECIFICATION IN ITS CURRENT, +OR IN ANY FUTURE FORM. IF ANY SUCH RIGHTS ARE DESCRIBED ON THE SPECIFICATION, +LICENSOR TAKES NO POSITION AS TO THE VALIDITY OR INVALIDITY OF SUCH +ASSERTIONS, OR THAT ALL SUCH ASSERTIONS THAT HAVE OR MAY BE MADE ARE SO +LISTED. + +4. TERMINATION OF LICENSE. + +In the event of a breach of this Agreement by Licensee or any of its employees or members, Licensor shall +give Licensee written notice and an opportunity to cure. If the breach is not cured within thirty (30) days +after written notice, or if the breach is of a nature that cannot be cured, then Licensor may immediately or +thereafter terminate the licenses granted in this Agreement. + +5. MISCELLANEOUS. + +All notices required under this Agreement shall be in writing, and shall be deemed effective five days from +deposit in the mails. Notices and correspondence to the NFC Forum address as it appears below. This +Agreement shall be construed and interpreted under the internal laws of the United States and the +Commonwealth of Massachusetts, without giving effect to its principles of conflict of law. + +NFC Forum, Inc. +401 Edgewater Place, Suite 600 +Wakefield, MA, USA 01880 + + +Contents + +## Contents + +#### 1 Introduction....................................................................................................1 + +#### 1.1 Objectives........................................................................................................................... 1 + +#### 1.2 Purpose ............................................................................................................................... 1 + +#### 1.2.1 Mission Statement and Goals................................................................................ 1 + +#### 1.3 References.......................................................................................................................... 2 + +#### 1.4 Administration.................................................................................................................... 2 + +#### 1.5 Special Word Usage........................................................................................................... 2 + +#### 1.6 Name and Logo Usage....................................................................................................... 3 + +#### 1.7 Intellectual Property........................................................................................................... 3 + +#### 1.8 Glossary.............................................................................................................................. 3 + +#### 2 Record Types.................................................................................................5 + +#### 2.1 NFC Forum Well-known Type.......................................................................................... 5 + +#### 2.1.1 NFC Forum Global Type...................................................................................... 5 + +#### 2.1.2 NFC Forum Local Type........................................................................................ 6 + +#### 2.2 NFC Forum External Type................................................................................................. 6 + +#### 2.3 Record Types Generic Requirements................................................................................. 7 + +#### 3 RTD Type Names...........................................................................................8 + +#### 3.1 Binary Encoding................................................................................................................. 9 + +#### 3.2 Percent Encoding in NFC Forum Types............................................................................ 9 + +#### 3.3 Equivalence of Record Type Names.................................................................................. 9 + +#### 3.4 RTD Type Names Requirements...................................................................................... 10 + +#### 4 Error Handling.............................................................................................11 + +#### 4.1 Illegal characters............................................................................................................... 11 + +#### 4.2 Unknown Record Types................................................................................................... 11 + +#### 4.3 Error Handling Requirements.......................................................................................... 11 + +#### A. Character Set for Record Types.................................................................12 + +#### B. Record Type Name Examples....................................................................13 + +#### C. Discussion on Associating Records.........................................................14 + +#### D. Revision History..........................................................................................16 + +## Figures + +#### Figure 1. NDEF Messages (Multiple)........................................................................................... 14 + +#### Figure 2. NDEF Message with Metadata...................................................................................... 14 + +NFC Record Type Definition (RTD) Page i + + +Tables + +## Tables + +#### Table 1. Definitions......................................................................................................................... 3 + +#### Table 2. Acronyms.......................................................................................................................... 4 + +#### Table 3. ASCII Character Chart.................................................................................................... 12 + +#### Table 4. Translating Record Type Names into Binary Representation......................................... 13 + +#### Table 5. Revision History.............................................................................................................. 16 + +## Test Requirements + +#### Test Requirements 1. Record Types Generic Requirements........................................................... 7 + +#### Test Requirements 2. RTD Type Names Requirements................................................................ 10 + +#### Test Requirements 3. Error Handling Requirements..................................................................... 11 + +NFC Record Type Definition (RTD) Page ii + + +Introduction + +## 1 Introduction + +The International Standard ISO/IEC 18092, Near Field Communication - Interface and Protocol +(NFCIP-1), defines an interface and protocol for simple wireless interconnection of closely +coupled devices operating at 13.56 MHz. + +The NFC Data Exchange Format (NDEF) specification defines a data format to exchange +information between an NFC Forum Device and another NFC Forum Device or an NFC Forum +Tag. The information that can be exchanged by means of NDEF may describe which services an +NFC Forum Device or NFC Forum Tag offers, it may contain application or service-specific +parameters and meta-data, or it may describe capabilities of NFC Forum Devices or NFC Forum +Tags. + +NDEF supports the use of standardized MIME content types and URIs to describe record content +which is specified outside of the NFC Forum. This specification describes two NFC Forum +specific types, known as “NFC Forum Well Known Types” and “NFC external types”. + +### 1.1 Objectives + +The NFC Data Exchange Format (NDEF) specification is a common data format for NFC Forum +Devices. + +The NFC Data Exchange Format specification defines the NDEF data structure format as well as +rules to construct a valid NDEF packet as a collection of NDEF records. Furthermore, it defines +the mechanism for constructing unique NDEF record type names by different parties, including a +format for NFC Forum well-known types. + +The NDEF specification defines only the data structure format to exchange application or service +specific data in an interoperable way, and it does not define any record types in detail. Specific +record types are defined in separate documents. + +The first part of this specification considers the type format of the NFC Forum well-known +types—that is, the contents of an NDEF Type field when the “TNF” header field value is 0x01 +(NFC Well-known Type). + +The second part of this specification considers the third party extension type known as an “NFC +external type”, which is signified by the TNF header field value of 0x04. + +### 1.2 Purpose + +#### 1.2.1 Mission Statement and Goals + +It is the mission of the NFC Forum to ensure interoperability of the NFC technology in a broad +variety of devices. The RTD specification is intended to support NFC-specific application and +service frameworks by providing a means for reservation of well-known record types, and third +party extension types. + +The RTD specification provides guidelines for the specification of well-known types for inclusion +in NDEF messages exchanged between NFC Forum devices and between NFC Forum Devices +and NFC Forum Tags. + +Actual type registrations are not provided in this specification but are expected to be included in +other documents. + +NFC Record Type Definition (RTD) Page 1 + + +Introduction + +### 1.3 References + +[ASCII] ANSI X3.4-1986, Coded Character Set 7-bit American Standard Code +for Information Interchange + +[ISO/IEC 18092] Information Technology- Telecommunications and information +exchange between systems- Near Field Communication - Interface and +Protocol (NFCIP-1). + +[NDEF] NFC Data Exchange Format, NFC Forum, 2006. + +[NFC Best] Best Practices for NFC Forum Terminology, NFC Forum, Technical +Committee Document. + +[RFC 2119] S. Bradner, “Key words for use in RFCs to Indicate Requirement +Levels”, RFC 2119, Harvard University, March 1997. +[http://www.apps.ietf.org/rfc/rfc2119.html](http://www.apps.ietf.org/rfc/rfc2119.html) + +[RFC 2046] N. Freed, N. Borenstein, “Multipurpose Internet Mail Extensions +(MIME) Part Two: Media Types” RFC 2046, Innosoft, First Virtual, +November 1996. + +[RFC 2141] R. Moats, “URN SYNTAX”, May 1997. + +[RFC 2234] D. Crocker, P. Overell, “Augmented BNF for Syntax Specifications: +ABNF”, November 1997. + +[RFC 3986] T. Berners-Lee, R. Fielding, L. Masinter, “Uniform Resource Identifiers +(URI): Generic Syntax”, RFC 3986, MIT/LCS, U.C. Irvine, Xerox +Corporation, January 2005. [http://www.apps.ietf.org/rfc/rfc3986.html](http://www.apps.ietf.org/rfc/rfc3986.html) + +### 1.4 Administration + +The NFC Record Type Definition (RTD) Specification is an open specification supported by the +Near Field Communication Forum, Inc., located at: + +401 Edgewater Place, Suite 600 +Wakefield, MA, 01880 + +Tel.: +1 781-876-8955 +Fax: +1 781-224-1239 + +[http://www.nfc-forum.org](http://www.nfc-forum.org) + +The Reference Applications Framework technical working group maintains this specification. + +This specification has been contributed to by Microsoft, Nokia, Panasonic, Philips, and Sony. + +### 1.5 Special Word Usage + +The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, +“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this +document are to be interpreted as described in RFC 2119. + +NFC Record Type Definition (RTD) Page 2 + + +Introduction + +### 1.6 Name and Logo Usage + +The Near Field Communication Forum’s policy regarding the use of the trademarks NFC Forum +and the NFC Forum logo is as follows: + +- Any company MAY claim compatibility with NFC Forum specifications, whether a member + of the NFC Forum or not. +- Permission to use the NFC Forum logos is automatically granted to designated members only + as stipulated on the most recent Membership Privileges document, during the period of time + for which their membership dues are paid. +- Member’s distributors and sales representatives MAY use the NFC Forum logo in promoting + member’s products sold under the name of the member. +- The logo SHALL be printed in black or in color as illustrated on the Logo Page that is + available from the NFC Forum at the address above. The aspect ratio of the logo SHALL be + maintained, but the size MAY be varied. Nothing MAY be added to or deleted from the + logos. +- Since the NFC Forum name is a trademark of the Near Field Communication Forum, the + following statement SHALL be included in all published literature and advertising material in + which the name or logo appears: + NFC Forum and the NFC Forum logo are trademarks of the Near Field Communication + Forum. + +### 1.7 Intellectual Property + +The NFC Record Type Definition (RTD) Specification conforms to the Intellectual Property +guidelines specified in the NFC Forum's Intellectual Property Right Policy, as approved on +November 9, 2004 and outlined in the NFC Forum Rules of Procedures, as approved on +December 17, 2004. + +### 1.8 Glossary + +This section defines all relevant terms and acronyms used in this specification. + +``` +Table 1. Definitions +``` +NDEF application + +``` +The logical, higher-layer application on an NFC Forum Device using NDEF to format +information for exchange with other NFC Forum Devices or NFC Forum Tags. Also user +application or NDEF user application. +``` +NDEF message + +``` +The basic message construct defined by this specification. An NDEF message contains +one or more NDEF records. +``` +NDEF record + +``` +An NDEF record contains a payload described by a type, a length, and an optional +identifier. +``` +NFC Record Type Definition (RTD) Page 3 + + +Introduction + +NDEF payload + +``` +The application data carried within an NDEF record. +``` +NDEF generator + +``` +An entity or module that encapsulates application-defined payloads within NDEF +messages. +``` +NDEF parser + +``` +An entity or module that parses NDEF messages and hands off the payloads to an NDEF +application. +``` +User Application + +``` +See NDEF Application. +``` +``` +Table 2. Acronyms +``` +NDEF NFC Data Exchange Format. See NFC DATA EXCHANGE FORMAT +(NDEF, Re-Draft Revision 0.96), NFC Forum Draft, October 2005 + +NID Namespace Identifier. Identifies uniquely an URN namespace. Please see +[RFC 2141] for a full definition. + +NSS Namespace Specific String. The rest of the URN after the NID. See +[RFC 2141] for a full definition. + +MIME Multipurpose Internet Mail Extensions. A standard specifying the format +of strongly-typed data transferred over the Internet. Defined in [RFC +2045-2049] + +RTD Record Type Definition. An NFC-specific record type and type name +which may be carried in an NDEF record with a TNF field value of 0x01 +(NFC Well-Known Type). + +URI Uniform Resource Identifier. A compact sequence of characters that +identifies an abstract or physical resource. [RFC 3986] Uniform +Resource Names (URNs) and Uniform Resource Locators (URLs) are +both forms of URI. + +URN Uniform Resource Name. A particular type of URI that is defined in +[RFC 2141]. + +NFC Record Type Definition (RTD) Page 4 + + +Record Types + +## 2 Record Types + +The record type string field of an NDEF record contains the name of the record type (called +“record type name”). Record type names are used by NDEF applications to identify the semantics +and structure of the record content. + +Record type names may be specified in several formats, called Type Name Formats, as signified +by the TNF field of the NDEF record header. Record type names may be MIME media types, +absolute URIs, NFC Forum external type names, or may be well-known NFC type names +(RTD’s, the subject of this specification). + +Each record type definition is identified by its record type name. + +Record type names can be defined by the NFC Forum and by third parties. In the following +sections, the rules governing the RTD type name space are defined. + +### 2.1 NFC Forum Well-known Type + +The NFC Forum Well-known Type is a dense format designed for tags and creating primitives for +certain common types. It is meant to be used in case there is no equivalent URI or MIME type +available, or when message size limitations require a very short name. + +An NFC Forum Well Known Type is identified inside an NDEF message by setting the TNF field +of a record to the value of 0x01, as defined in the NDEF specification. + +An NFC Forum Well-Known Type is a URN as defined by [RFC 2141], with the namespace +identifier (NID) “nfc”. + +The Namespace Specific String of the NFC Well Known Type URN is prefixed with “wkt:”. +However, when encoded in an NDEF message, the Well Known Type MUST be written as a +relative-URI construct [RFC 3896], omitting the NID and the “wkt:” –prefix. + +For example, the Well Known Type “urn:nfc:wkt:a” would be encoded as “a”. The Well Known +Type “urn:nfc:wkt:Very-complicated-type” would be encoded as “Very-complicated-type”. + +There are two kinds of NFC Forum Well Known Types detailed in the sections below. For +brevity, we exclude the URN NID and the NSS prefix from the examples. + +For a definition of the character ranges used in the Well Known Types, please see chapter 3. + +#### 2.1.1 NFC Forum Global Type + +The NFC Forum is responsible for defining and managing NFC Forum Global Types. Other +parties MUST NOT define or redefine these. + +An NFC Forum Global Type SHALL start with an upper-case letter (character range ). + +Examples of NFC Forum Global Types: “U”, “Cfq”, “Trip-to-Texas”. + +NFC Record Type Definition (RTD) Page 5 + + +Record Types + +#### 2.1.2 NFC Forum Local Type + +NFC Forum Local Types SHALL start with a character in sets or . + +NFC Forum Local Types are available for use within the context of another record. A processing +application MUST NOT process these types when application context is not available. Local +types are used whenever the burden of using a long, domain name–based external type is too +much, and there is no need to define its meaning outside of the local context. + +An RTD or an application defines the context for the interpretation for a Local Type. A Local +Type MAY be reused by another application in a different context and with different content. + +Examples of NFC Forum Local Types: “0”, “foo”, “u”. + +### 2.2 NFC Forum External Type + +The External Type Name is meant for organizations that wish to self-allocate a name space to be +used for their own purposes. + +An External Type is identified in an NDEF record by setting the TNF field value to 0x04, as +defined in the NDEF specification [NDEF]. + +The External Type is, much like a Well Known Type, an URN, with the NID of “nfc”. However, +the NSS specific part is put into another namespace named “ext”. A canonical version of the +External Type Name would look like: + +``` +“urn:nfc:ext:example.com:f” +``` +The External Type Name MUST be formed by taking the domain name of the issuing +organization, adding a colon, and then adding the type name as managed by the organization. + +As with Well Known Types, the binary encoding of External Type Name inside NDEF messages +MUST omit the NID and the NSS prefix of “ext”. + +NFC Record Type Definition (RTD) Page 6 + + +Record Types + +### 2.3 Record Types Generic Requirements + +``` +Test Requirements 1. Record Types Generic Requirements +``` +NFC Forum standardized types defined as RTD records SHALL use NFC Forum Well- +Known type names. + +When packaged into NDEF records, NFC Forum standardized types defined as RTD records +SHALL be signified in the NDEF record header by the Type Name Format (TNF) field value +of 0x01 (NFC Forum Well-Known Type). + +An NFC Forum Well Known Type SHALL be a URN with the “urn:nfc:wkt:” prefix. + +An NFC Forum Global Type MUST NOT be defined or redefined by other parties than NFC +Forum. + +An NFC Forum Global Type SHALL start with a character in the range as defined in +Chapter 3. + +An NFC Forum Local Type SHALL start with a character in the range or +as defined in Chapter 3. + +A processing application MUST NOT process a NFC Forum Local Type if an application +context is not available. + +An NFC Forum Local Type MAY be reused by another application in a different context and +with different content. + +An NFC Forum External Type SHALL be identified with the TNF field value of 0x04. + +An NFC Forum External Type SHALL be a URN with the prefix of “urn:nfc:ext:”. + +In the NDEF binary format, the URN prefix MUST NOT be used. + +The External Type MUST be formed by taking the domain name of the issuing organization, +adding a colon, and then adding a type name. An External Type MUST include a colon and a +non-zero length type name. + +NFC Record Type Definition (RTD) Page 7 + + +RTD Type Names + +## 3 RTD Type Names + +This section defines the normative requirements for the NFC Forum Well-Known Type Names +(below: RTD-URI). The language used is the ABNF format as defined in RFC 2234 [RFC 2234]. + +RTD-URI = “urn:nfc:” nfc-nss + +nfc-nss = wkt-nss / external-nss + +wkt-nss = wkt-id “:” WKT-type + +external-nss = external-id “:” external-type + +wkt-id = “wkt” + +external-id = “ext” + +WKT-type = local / global + +local = ( lower / number ) *WKT-char + +global = upper *WKT-char + +external-type = dns-part “:” name-part + +dns-part = 1*DNS-char + +name-part = 1*WKT-char + +WKT-char = upper / lower / number / other + +DNS-char = upper / lower / number / “.” / “-” + +upper = “A” / “B” / “C” / “D” / “E” / “F” / “G” / “H” / +“I” / “J” / “K” / “L” / “M” / “N” / “O” / “P” / +“Q” / “R” / “S” / “T” / “U” / “V” / “W” / “X” / +“Y” / “Z” + +lower = “a” / “b” / “c” / “d” / “e” / “f” / “g” / “h” / +“i” / “j” / “k” / “l” / “m” / “n” / “o” / “p” / +“q” / “r” / “s” / “t” / “u” / “v” / “w” / “x” / +“y” / “z” + +number = “0” / “1” / “2” / “3” / “4” / “5” / “6” / “7” / +“8” / “9” + +other = “(“ / “)” / “+” / “,” / “-” / +“:” / “=“ / “@” / “;” / “$” / +“_” / “!” / “*” / “'“ / “.” + +reserved = “%” / “/” / “?” / “#” + +NFC Record Type Definition (RTD) Page 8 + + +RTD Type Names + +### 3.1 Binary Encoding + +The binary encoding of Well Known Types and External Type Names for NDEF MUST be done +according to the ASCII chart in Appendix A. + +The URN NID and the NFC NSS prefixes MUST NOT be included in the binary NDEF format. +(However, if RTDs are used in other formats, such as XML, the URNs SHOULD be given in the +absolute URN format.) + +NOTE: This specification does not define legal characters for any particular record content. +Record content is specified in other documents, specific to those record types. + +### 3.2 Percent Encoding in NFC Forum Types + +To help define equivalence rules for NFC Forum Well Known Types, NFC Forum will not issue +a Global Type Name using percent-encoding as defined in [RFC 2141]. Any Local Type Name +used by third parties MUST NOT use the percent encoding. + +External Types SHOULD NOT use the percent encoding. However, an application using such an +external type MUST first encode the string in UTF-8 before converting it to the percent encoding. + +### 3.3 Equivalence of Record Type Names + +The comparison of record type names is done on a character-by-character basis. + +Two Well Known Type names MUST be compared in a case-sensitive manner. Because of the +fact that the encoding is fixed to US-ASCII, it also implies that two Well Known Types MUST be +considered equivalent if and only if their binary representations are identical. + +Example: + +“Foobar” +“fooBar” +“fOoBaR” +“foobar” + +The four examples above are all different Well Known Type names. + +Two External Type Names MUST be compared in a case-insensitive manner. Example: + +“example.com:foobar” +“Example.com:foobar” +“Example.COM:Foobar” +“eXaMpLe.CoM:fOoBaR” + +The four examples above represent all the same External Type Name. + +NFC Record Type Definition (RTD) Page 9 + + +RTD Type Names + +### 3.4 RTD Type Names Requirements + +``` +Test Requirements 2. RTD Type Names Requirements +``` +The binary encoding of Well Known Types (including Global and Local Names) and External +Type names MUST be done according to the ASCII chart in Appendix A. + +Well Known Types (including Global and Local Names) MUST NOT use the percent- +encoding as defined by RFC 2141. + +External types SHOULD NOT use the percent encoding as defined by RFC 2141. + +Two Well Known Types (including Global and Local Names) MUST be compared on a case- +sensitive, character-by-character basis. In other words, two Well Known Types MUST be +considered equal if and only if their binary representations are identical. + +Two External Types MUST be compared on a case insensitive, character-by-character basis. + +NFC Record Type Definition (RTD) Page 10 + + +Error Handling + +## 4 Error Handling + +### 4.1 Illegal characters + +A record with a type name containing characters outside of the valid range of characters defined +in Chapter 3 MUST be ignored. + +### 4.2 Unknown Record Types + +Applications MUST ignore records which have a Well Known Type or an External Type that +they do not recognize. + +### 4.3 Error Handling Requirements + +``` +Test Requirements 3. Error Handling Requirements +``` +Any character not defined as a valid character in Chapter 3 SHALL be considered an illegal +character in a record type name. + +Records containing illegal characters in the record type name MUST be ignored. + +An application that does not recognize a record type name MUST ignore the entire record. + +NFC Record Type Definition (RTD) Page 11 + + +``` +Character Set for Record Types +``` +## A. Character Set for Record Types + +``` +Record type names SHALL be formed of characters from of the US ASCII [ASCII] character set. +Characters in the range [0-31] and 127 decimal, as shown in the following table, SHALL NOT be +used in record type names. +Table 3. ASCII Character Chart +``` +Binary Dec Hex Graph. Binary Dec Hex Graph. Binary Dec Hex Graph. + +0010 0000 32 20 (blank)^ 0100 0000 64 40 @^ 0110 0000 96 60 ` + +0010 0001 33 21!^ 0100 0001 65 41 A^ 0110 0001 97 61 a + +0010 0010 34 22 “^ 0100 0010 66 42 B^ 0110 0010 98 62 b + +0010 0011 35 23 #^ 0100 0011 67 43 C^ 0110 0011 99 63 c + +0010 0100 36 24 $^ 0100 0100 68 44 D^ 0110 0100 100 64 d + +0010 0101 37 25 %^ 0100 0101 69 45 E^ 0110 0101 101 65 e + +0010 0110 38 26 &^ 0100 0110 70 46 F^ 0110 0110 102 66 f + +0010 0111 39 27 ’^ 0100 0111 71 47 G^ 0110 0111 103 67 g + +0010 1000 40 28 (^ 0100 1000 72 48 H^ 0110 1000 104 68 h + +0010 1001 41 29 )^ 0100 1001 73 49 I^ 0110 1001 105 69 i + +0010 1010 42 2A *^ 0100 1010 74 4A J^ 0110 1010 106 6A j + +0010 1011 43 2B +^ 0100 1011 75 4B K^ 0110 1011 107 6B k + +0010 1100 44 2C , , 0100 1100 76 4C L^ 0110 1100 108 6C l + +0010 1101 45 2D - 0100 1101 77 4D M^ 0110 1101 109 6D m + +0010 1110 46 2E.^ 0100 1110 78 4E N^ 0110 1110 110 6E n + +0010 1111 47 2F /^ 0100 1111 79 4F O^ 0110 1111 111 6F o + +0011 0000 48 30 0 0101 0000 80 50 P^ 0111 0000 112 70 p + +0011 0001 49 31 1 0101 0001 81 51 Q^ 0111 0001 113 71 q + +0011 0010 50 32 2 0101 0010 82 52 R^ 0111 0010 114 72 r + +0011 0011 51 33 3 0101 0011 83 53 S^ 0111 0011 115 73 s + +0011 0100 52 34 4 0101 0100 84 54 T^ 0111 0100 116 74 t + +0011 0101 53 35 5 0101 0101 85 55 U^ 0111 0101 117 75 u + +0011 0110 54 36 6 0101 0110 86 56 V^ 0111 0110 118 76 v + +0011 0111 55 37 7 0101 0111 87 57 W^ 0111 0111 119 77 w + +0011 1000 56 38 8 0101 1000 88 58 X^ 0111 1000 120 78 x + +0011 1001 57 39 9 0101 1001 89 59 Y^ 0111 1001 121 79 y + +0011 1010 58 3A :^ 0101 1010 90 5A Z^ 0111 1010 122 7A z + +0011 1011 59 3B ;^ 0101 1011 91 5B [^ 0111 1011 123 7B { + +0011 1100 60 3C <^ 0101 1100 92 5C \^ 0111 1100 124 7C | + +0011 1101 61 3D =^ 0101 1101 93 5D ]^ 0111 1101 125 7D } + +0011 1110 62 3E >^ 0101 1110 94 5E ^^ 0111 1110 126 7E ~ + +0011 1111 63 3F?^ 0101 1111 95 5F _^ + +``` +NFC Record Type Definition (RTD) Page 12 +``` + +Record Type Name Examples + +## B. Record Type Name Examples + +The contents of this appendix are informative and describe examples for encoding and comparing +record type names into their binary representation. + +An example of translating a record type name into binary representation: + +``` +Table 4. Translating Record Type Names into Binary Representation +``` +``` +String Representation Binary Representation (as hexadecimal) +Sms 53 6D 73 +``` +``` +sms 73 6D 73 +``` +To encode the binary representation of the type names, each character from the string +representation is replaced by its binary value from Appendix A. + +In this example, the two record type names are considered non-equivalent since their binary +representations are not identical. The case-sense of letters in the string, white space, and other +language comparison rules are not considered when comparing type strings for equivalence. Only +the binary representations are considered. + +NFC Record Type Definition (RTD) Page 13 + + +Discussion on Associating Records + +## C. Discussion on Associating Records + +The contents of this appendix are informative. + +There are two basic ways to associate NDEF records to each other. The first one is called +“association by reference”, which is amounts to a flat hierarchy or a list of objects. + +When associating records by reference, the context is typically given by the first record in the +message. This is the same association model that is used by MIME. For example, if you wish to +represent an email message with two PNG attachments as an NDEF message, you first send the +email message in one record (typed message/rfc822), then the first PNG image as a separate +record (image/png), and the second PNG image (again, image/png). To illustrate: + +``` +NDEF MESSAGE +Email (message/rfc822) Pic1.png (image/png) Pic2.png (image/png) +``` +``` +Figure 1. NDEF Messages (Multiple) +``` +This method allows an application to lift the PNG images off the message, even if it does not +understand the email message. In general, when designing your own record types, you should +choose association by reference if your message parts would be valuable even on their own, i.e., +even if the context is not understood. Association by reference is also a good model if you are +moving a large amount of data because it allows you to take advantage of the chunking feature of +NDEF. In addition, it also allows the processing to start at the receiver end before the message is +finished (this is one of the reasons why it is good to declare the context at the beginning of the +message). + +The second way is called “association by containment”. This is a hierarchical model (not entirely +unlike XML or HTML), where the content portion of an NDEF records contains an NDEF +message. This is very useful in the case where you wish to imply a stronger relationship between +records, or need to serialize information that is already in a hierarchical format. Also, if you are +going to send multiple objects of the same type within the message, you probably wish to use an +containment model, and then string them together in a list(so yes, it is possible and very sensible +to mix these models). + +For example, the Smart Poster record defines a URI plus some added metadata about that URI. +The added metadata is not useful to an application without the URI itself, and in fact, it would be +relatively meaningless. To illustrate: + +``` +NDEF Message +Sp (Smart Poster) application/vcard +URI Text Action Configuration vCard data +``` +``` +Figure 2. NDEF Message with Metadata +``` +In this case, there are two records in the NDEF message. The first one is a Smart Poster +containing a URI, a Text record for a title, an action, and a configuration record; whereas the +other one is just a normal vCard (using the vCard standard). (In this case, there is no particular +context defined for the vCard, so an application may either ignore it or use it for some purpose; +this is an implementation detail. In general, putting records describing different things and +assuming some particular context or processing model will probably result in interoperability +trouble.) + +NFC Record Type Definition (RTD) Page 14 + + +Discussion on Associating Records + +Anyway, since the Text, Action, and Configuration are so tightly coupled with the URI (the URI +might not even be fetchable without the proper configuration, if the config defines a local access +point), they work better using a containment model than a reference model. + +Neither of these examples displayed any use of the ID field, which can be used in both models +with equal efficiency. In association by reference, the first record typically lists the IDs that it +uses and defines the context that way; in association by containment, the IDs would typically be +used to signify the role of a record (e.g., “A record with an ID of 'config' shall be used for +defining an access point.”) + +Of course, an application is free to mix-and-match these association types. There is no hard-and- +fast rule to say which one is better in a given situation, and as designed, this allows maximum +flexibility to the application developer. + +A third, but deprecated, practice would be using ordering (i.e., record #1 would always signify +something, record #2 something else, record #3 again something else), but this, in general, is not +a good idea, since you cannot rely on any particular behavior of a NDEF processor. It could be +that by the time your application receives the NDEF message, records may have been inserted or +removed. Do not rely on any implementation-specific behavior. This seems obvious to any +seasoned developer, but it is easy to forget in the rush of a deadline. + +The advice in this discussion is offered because it is likely that developers at some point face the +need to associate NDEF records with each other, and it is good that some of the best practices and +conventions are laid out for all to see. Reading a new specification can be difficult, and hopefully +discussion such as this will ease the work of the developer. + +NFC Record Type Definition (RTD) Page 15 + + +Revision History + +## D. Revision History + +The following table outlines the revision history of the RTD Technical Specification. + +``` +Table 5. Revision History +``` +Document Name Revision and +Release Date + +``` +Status Change notice Supersedes +``` +NFCForum-TS- +RTD_1.0 + +``` +1.0, July 2006 Final none +``` +NFC Record Type Definition (RTD) Page 16 + + +# Text Record Type Definition + +## Technical Specification + +## NFC Forum + +#### TM + +## RTD-Text 1.0 + +## NFCForum-TS-RTD_Text_1.0 + +## 2006-07-24 + + +##### RESTRICTIONS ON USE + +This specification is copyright © 2005-2006 by the NFC Forum, and was made available pursuant to a +license agreement entered into between the recipient (Licensee) and NFC Forum, Inc. (Licensor) and may +be used only by Licensee, and in compliance with the terms of that license agreement (License). If you are +not the Licensee, you are not authorized to make any use of this specification. However, you may obtain a +copy at the following page of Licensor's Website: [http://www.nfc-forum.org/resources/spec_license](http://www.nfc-forum.org/resources/spec_license) after +entering into and agreeing to such license terms as Licensor is then requiring. On the date that this +specification was downloaded by Licensee, those terms were as follows: + +1. LICENSE GRANT. + +Licensor hereby grants Licensee the right, without charge, to copy (for internal purposes only) and share +the Specification with Licensee's members, employees and consultants (as appropriate). This license grant +does not include the right to sublicense, modify or create derivative works based upon the Specification. + +2. NO WARRANTIES. + +THE SPECIFICATION IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, COMPLETENESS AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL LICENSOR, ITS +MEMBERS OR ITS CONTRIBUTORS BE LIABLE FOR ANY CLAIM, OR ANY DIRECT, SPECIAL, +INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING +FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH +THE USE OR PERFORMANCE OF THE SPECIFICATION. + +3. THIRD PARTY RIGHTS. + +Without limiting the generality of Section 2 above, LICENSOR ASSUMES NO RESPONSIBILITY TO +COMPILE, CONFIRM, UPDATE OR MAKE PUBLIC ANY THIRD PARTY ASSERTIONS OF +PATENT OR OTHER INTELLECTUAL PROPERTY RIGHTS THAT MIGHT NOW OR IN THE +FUTURE BE INFRINGED BY AN IMPLEMENTATION OF THE SPECIFICATION IN ITS CURRENT, +OR IN ANY FUTURE FORM. IF ANY SUCH RIGHTS ARE DESCRIBED ON THE SPECIFICATION, +LICENSOR TAKES NO POSITION AS TO THE VALIDITY OR INVALIDITY OF SUCH +ASSERTIONS, OR THAT ALL SUCH ASSERTIONS THAT HAVE OR MAY BE MADE ARE SO +LISTED. + +4. TERMINATION OF LICENSE. + +In the event of a breach of this Agreement by Licensee or any of its employees or members, Licensor shall +give Licensee written notice and an opportunity to cure. If the breach is not cured within thirty (30) days +after written notice, or if the breach is of a nature that cannot be cured, then Licensor may immediately or +thereafter terminate the licenses granted in this Agreement. + +5. MISCELLANEOUS. + +All notices required under this Agreement shall be in writing, and shall be deemed effective five days from +deposit in the mails. Notices and correspondence to the NFC Forum address as it appears below. This +Agreement shall be construed and interpreted under the internal laws of the United States and the +Commonwealth of Massachusetts, without giving effect to its principles of conflict of law. + +NFC Forum, Inc. +401 Edgewater Place, Suite 600 +Wakefield, MA, USA 01880 + + +Contents + +## Contents + +#### 1 Overview........................................................................................................1 + +#### 1.1 Objectives........................................................................................................................... 1 + +#### 1.2 Purpose ............................................................................................................................... 1 + +#### 1.2.1 Mission Statement and Goals................................................................................ 1 + +#### 1.3 References.......................................................................................................................... 1 + +#### 1.4 Administration.................................................................................................................... 1 + +#### 1.5 Special Word Usage........................................................................................................... 2 + +#### 1.6 Name and Logo Usage....................................................................................................... 2 + +#### 1.7 Intellectual Property........................................................................................................... 2 + +#### 1.8 Acronyms........................................................................................................................... 2 + +#### 2 Text Record....................................................................................................3 + +#### 2.1 Introduction........................................................................................................................ 3 + +#### 2.2 Dependencies...................................................................................................................... 3 + +#### 2.3 Security Considerations...................................................................................................... 3 + +#### 3 NDEF structure..............................................................................................4 + +#### 3.1 Messaging Sequence.......................................................................................................... 4 + +#### 3.2 Records Mapping............................................................................................................... 4 + +#### 3.2.1 Syntax.................................................................................................................... 4 + +#### 3.2.2 Structure................................................................................................................ 5 + +#### 3.3 Language Codes................................................................................................................. 5 + +#### 3.4 UTF-16 Byte Order............................................................................................................ 5 + +#### A. Example UTF-8 Encoding.............................................................................6 + +#### B. Revision History............................................................................................7 + +## Tables + +#### Table 1. Acronyms.......................................................................................................................... 2 + +#### Table 2. Text Record Content Syntax............................................................................................. 4 + +#### Table 3. Status Byte Encodings....................................................................................................... 4 + +#### Table 4. Example: “Hello, world!”.................................................................................................. 6 + +#### Table 5. Revision History................................................................................................................ 7 + +Text Record Type Definition Page i + + +Overview + +## 1 Overview + +The Text Record Type Description defines an NFC Forum Well Known Type [NFC RTD] for +plain text data. It may be used as free form text descriptions of other objects on an RFID tag. + +### 1.1 Objectives + +The objective of this document is to function as a normative reference to the Text RTD. + +### 1.2 Purpose + +#### 1.2.1 Mission Statement and Goals + +The Text RTD was designed to be used as a general purpose text field to add metadata to things +such as URLs. It needs to provide a lightweight component with clearly defined semantics. + +The goal is not to replace text/plain, but to define a clear subset that can be used in cases where +there is not much space to be used, and to cover the most probable use cases. + +The Text RTD must work well for non-western languages also, and it needs to include the +language information for localization purposes so that the language can be identified and served +to the user. + +### 1.3 References + +[NDEF] “NFC Data Exchange Format Specification”, NFC Forum, 2006. + +[NFC RTD] “NFC Record Type Definition (RTD) Specification”, NFC Forum, 2006. + +[RFC 2119] S. Bradner, “Key words for use in RFCs to Indicate Requirement +Levels”, RFC 2119, Harvard University, March 1997. +[http://www.apps.ietf.org/rfc/rfc2119.html](http://www.apps.ietf.org/rfc/rfc2119.html) + +[RFC 3066] H. Alvestrand, “Tags for the Identification of Languages”, RFC 3066, +Cisco Systems, January 2001. [http://www.faqs.org/rfcs/rfc3066.html](http://www.faqs.org/rfcs/rfc3066.html) + +[RFC 3066bis] A. Phillips, M. Davis, “Tags for the Identification of Languages”. IETF +Draft. [http://www.ietf.org/internet-drafts/draft-ietf-ltru-registry-14.txt](http://www.ietf.org/internet-drafts/draft-ietf-ltru-registry-14.txt) + +[UNICODE] “The Unicode 4.0.1 standard”. +[http://www.unicode.org/versions/Unicode4.0.1/](http://www.unicode.org/versions/Unicode4.0.1/) + +### 1.4 Administration + +The Text RTD Specification is an open specification supported by the Near Field Communication +Forum, Inc., located at: + +401 Edgewater Place, Suite 600 +Wakefield, MA, 01880 + +Tel.: +1 781-876-8955 +Fax: +1 781-224-1239 + +[http://www.nfc-forum.org](http://www.nfc-forum.org) + +The Reference Applications Framework technical working group maintains this specification. + +Text Record Type Definition Page 1 + + +Overview + +### 1.5 Special Word Usage + +The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, +“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this +document are to be interpreted as described in RFC 2119. + +### 1.6 Name and Logo Usage + +The Near Field Communication Forum’s policy regarding the use of the trademarks NFC Forum +and the NFC Forum logo is as follows: + +- Any company MAY claim compatibility with NFC Forum specifications, whether a member + of the NFC Forum or not. +- Permission to use the NFC Forum logos is automatically granted to designated members only + as stipulated on the most recent Membership Privileges document, during the period of time + for which their membership dues are paid. +- Member’s distributors and sales representatives MAY use the NFC Forum logo in promoting + member’s products sold under the name of the member. +- The logo SHALL be printed in black or in color as illustrated on the Logo Page that is + available from the NFC Forum at the address above. The aspect ratio of the logo SHALL be + maintained, but the size MAY be varied. Nothing MAY be added to or deleted from the + logos. +- Since the NFC Forum name is a trademark of the Near Field Communication Forum, the + following statement SHALL be included in all published literature and advertising material in + which the name or logo appears: + NFC Forum and the NFC Forum logo are trademarks of the Near Field Communication + Forum. + +### 1.7 Intellectual Property + +The Text RTD Specification conforms to the Intellectual Property guidelines specified in the +NFC Forum's Intellectual Property Right Policy, as approved on November 9, 2004 and outlined +in the NFC Forum Rules of Procedures, as approved on December 17, 2004. + +### 1.8 Acronyms + +This table defines all relevant terms and acronyms used in this specification. + +``` +Table 1. Acronyms +``` +Acronyms Definition + +LSB Least Significant Bit + +NDEF NFC Data Exchange Format + +RFU Reserved for Future Use + +RTD Record Type Description + +URI Uniform Resource Identifier + +URL Uniform Resource Locator (this is a special case of an URI) + +Text Record Type Definition Page 2 + + +Text Record + +## 2 Text Record + +### 2.1 Introduction + +The “Text” record contains freeform plain text. It can be used to describe a service or the contents +of the tag, for example. + +The Text record MAY appear as a sole record in an NDEF message [NDEF], but in this case the +behavior is undefined and left to the application to handle. Typically, the Text record should be +used in conjunction with other records to provide explanatory text. + +### 2.2 Dependencies + +There are no dependencies for the Text element. + +### 2.3 Security Considerations + +It is possible to write different text on the Text record than what the tag actually does, and thus +spoof the user into doing something else than what he actually wanted (i.e., phishing). Thus it is a +good idea for the user interface to use the Text field only as an informative field. + +Text Record Type Definition Page 3 + + +NDEF structure + +## 3 NDEF structure + +### 3.1 Messaging Sequence + +There is no particular messaging sequence available for this RTD. + +### 3.2 Records Mapping + +#### 3.2.1 Syntax + +The NFC Forum Well Known Type [NDEF], [NFC RTD] for the Text record is “T” (in NFC +binary encoding: 0x54). + +The data content is as follows: + +``` +Table 2. Text Record Content Syntax +``` +``` +Offset +(bytes) +``` +``` +Length +(bytes) +``` +``` +Content +``` +``` +0 1 Status byte. See Table 3. +``` +``` +1 ISO/IANA language code. Examples: “fi”, “en-US”, “fr- +CA”, “jp”. The encoding is US-ASCII. +n+1 The actual text. Encoding is either UTF-8 or UTF-16, +depending on the status bit. +``` +The Status bit encodings are as described in Table 3. Any value marked RFU SHALL be ignored, +and any software writing these bits SHALL use the value zero for these bits. + +``` +Table 3. Status Byte Encodings +``` +``` +Bit number (0 +is LSB) +``` +``` +Content +``` +``` +7 0: The text is encoded in UTF-8 +1: The text is encoded in UTF16 +``` +``` +6 RFU (MUST be set to zero) +``` +``` +5..0 The length of the IANA language code. +``` +The contents of the text field MAY be shown to the user. If multiple 'T' records exist, the one +with the closest matching language to the user preference SHOULD be displayed. To have +multiple text elements within a single application, context with the same language code SHOULD +be considered an error. + +Text Record Type Definition Page 4 + + +NDEF structure + +Control characters (0x00-0x1F in UTF-8) should be removed prior to display, except for newline, +line feed (0x0D, 0x0A) and tab (0x08) characters. Markup MUST NOT be embedded (please use +the “text/xhtml” or other suitable MIME types). The Text record should be considered to be equal +to the MIME type “text/plain; format=fixed”. + +Line breaks in the text MUST be represented using the CRLF (so-called DOS convention, the +sequence 0x0D,0x0A in UTF-8). The device may deal with the tab character as it wishes. + +White space other than newline and tab SHOULD be collapsed, i.e., multiple space characters are +to be considered a single space character. + +To find the length of the actual text in bytes, you calculate the length via “m=(length of the +payload – length of the IANA language code – 1)” + +#### 3.2.2 Structure + +If the Text record describes an element, it SHOULD occur in the NDEF record list before the +element it is describing. This makes it faster to find and display to the user if the element is very +large. + +### 3.3 Language Codes + +All language codes MUST be done according to RFC 3066 [RFC3066]. The language code MAY +NOT be omitted. + +The language code length is encoded in the six least significant bits of the status byte. Thus it is +easy to find by masking the status byte with the value 0x3F. + +The language code is typically either two characters or five characters, though in the future, it is +likely that it will be possible to have longer codes. At this time, IETF is considering an extension +to RFC 3066 which will cover language codes up to 33 bytes in length [RFC 3066bis]. The two- +character version disregards any dialects, and thus is used most often; for example, “fi” for +Finnish, “jp” for Japanese, “fr” for French. However, in some cases you might want to +differentiate between variants of the same language, such as providing US-English and British +English versions via “en-US” and “en-UK” respectively. + +### 3.4 UTF-16 Byte Order + +The Unicode Byte-Order-Mark (BOM) in the actual string MUST be tolerated (i.e. no error +condition). When generating a Text record, the BOM MAY be omitted. If the BOM is omitted, +the byte order shall be big-endian (UTF-16 BE). + +Text Record Type Definition Page 5 + + +Example UTF-8 Encoding + +## A. Example UTF-8 Encoding + +Here’s an example on how the English phrase “Hello, world!” would be encoded in UTF-8: + +``` +Table 4. Example: “Hello, world!” +``` +``` +Offset Content Explanation Syntactical info +``` +``` +0 N/A IL flag = 0 (no ID field), SF=1 +(Short format) +1 0x01 Length of the record name +``` +``` +2 0x10 The length of the payload data (16 +bytes) +3 “T” The binary encoding of the name, +as defined in [1] +``` +``` +NDEF record header +``` +``` +4 0x02 Status byte: This is UTF-8, and +has a two-byte language code +5 “en” “en” is the ISO code for “English” +``` +``` +Payload +``` +``` +7 “Hello, +world!” +``` +``` +UTF-8 string “Hello, world!” +The actual body text. +``` +Text Record Type Definition Page 6 + + +Revision History + +## B. Revision History + +The following table outlines the revision history of the Text RTD Technical Specification. + +``` +Table 5. Revision History +``` +Document +Name + +``` +Revision and +Release Date +``` +``` +Status Change Notice Supersedes +``` +NFCForum-TS- +RTD_Text_1.0 + +``` +1.0, July 2006 None First Revision +``` +Text Record Type Definition Page 7 + + +# URI Record Type Definition + +## Technical Specification + +## NFC Forum + +#### TM + +## RTD-URI 1.0 + +## NFCForum-TS-RTD_URI_1.0 + +## 2006-07-24 + + +##### RESTRICTIONS ON USE + +This specification is copyright © 2005-2006 by the NFC Forum, and was made available pursuant to a +license agreement entered into between the recipient (Licensee) and NFC Forum, Inc. (Licensor) and may +be used only by Licensee, and in compliance with the terms of that license agreement (License). If you are +not the Licensee, you are not authorized to make any use of this specification. However, you may obtain a +copy at the following page of Licensor's Website: [http://www.nfc-forum.org/resources/spec_license](http://www.nfc-forum.org/resources/spec_license) after +entering into and agreeing to such license terms as Licensor is then requiring. On the date that this +specification was downloaded by Licensee, those terms were as follows: + +1. LICENSE GRANT. + +Licensor hereby grants Licensee the right, without charge, to copy (for internal purposes only) and share +the Specification with Licensee's members, employees and consultants (as appropriate). This license grant +does not include the right to sublicense, modify or create derivative works based upon the Specification. + +2. NO WARRANTIES. + +THE SPECIFICATION IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, COMPLETENESS AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL LICENSOR, ITS +MEMBERS OR ITS CONTRIBUTORS BE LIABLE FOR ANY CLAIM, OR ANY DIRECT, SPECIAL, +INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING +FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH +THE USE OR PERFORMANCE OF THE SPECIFICATION. + +3. THIRD PARTY RIGHTS. + +Without limiting the generality of Section 2 above, LICENSOR ASSUMES NO RESPONSIBILITY TO +COMPILE, CONFIRM, UPDATE OR MAKE PUBLIC ANY THIRD PARTY ASSERTIONS OF +PATENT OR OTHER INTELLECTUAL PROPERTY RIGHTS THAT MIGHT NOW OR IN THE +FUTURE BE INFRINGED BY AN IMPLEMENTATION OF THE SPECIFICATION IN ITS CURRENT, +OR IN ANY FUTURE FORM. IF ANY SUCH RIGHTS ARE DESCRIBED ON THE SPECIFICATION, +LICENSOR TAKES NO POSITION AS TO THE VALIDITY OR INVALIDITY OF SUCH +ASSERTIONS, OR THAT ALL SUCH ASSERTIONS THAT HAVE OR MAY BE MADE ARE SO +LISTED. + +4. TERMINATION OF LICENSE. + +In the event of a breach of this Agreement by Licensee or any of its employees or members, Licensor shall +give Licensee written notice and an opportunity to cure. If the breach is not cured within thirty (30) days +after written notice, or if the breach is of a nature that cannot be cured, then Licensor may immediately or +thereafter terminate the licenses granted in this Agreement. + +5. MISCELLANEOUS. + +All notices required under this Agreement shall be in writing, and shall be deemed effective five days from +deposit in the mails. Notices and correspondence to the NFC Forum address as it appears below. This +Agreement shall be construed and interpreted under the internal laws of the United States and the +Commonwealth of Massachusetts, without giving effect to its principles of conflict of law. + +NFC Forum, Inc. +401 Edgewater Place, Suite 600 +Wakefield, MA, USA 01880 + + +Contents + +## Contents + +#### 1 Overview........................................................................................................1 + +#### 1.1 Objectives........................................................................................................................... 1 + +#### 1.2 Purpose ............................................................................................................................... 1 + +#### 1.2.1 Mission Statement and Goals................................................................................ 1 + +#### 1.3 References.......................................................................................................................... 1 + +#### 1.4 Administration.................................................................................................................... 1 + +#### 1.5 Special Word Usage........................................................................................................... 2 + +#### 1.6 Name and Logo Usage....................................................................................................... 2 + +#### 1.7 Intellectual Property........................................................................................................... 2 + +#### 1.8 Acronyms........................................................................................................................... 3 + +#### 2 URI Service.....................................................................................................4 + +#### 2.1 NDEF Message Sequences................................................................................................. 4 + +#### 2.2 Dependencies...................................................................................................................... 4 + +#### 3 NDEF Structure..............................................................................................5 + +#### 3.1 Messaging Sequence.......................................................................................................... 5 + +#### 3.2 Records Mapping............................................................................................................... 5 + +#### 3.2.1 URI Record Type.................................................................................................. 5 + +#### 3.2.2 URI Identifier Code............................................................................................... 5 + +#### 3.2.3 URI Field............................................................................................................... 7 + +#### 4 Handling Guideline........................................................................................8 + +#### A. Examples........................................................................................................9 + +#### A.1 Simple URL with No Substitution..................................................................................... 9 + +#### A.2 Storing a Telephone Number............................................................................................. 9 + +#### A.3 Storing a Proprietary URI on the Tag............................................................................... 10 + +#### B. Revision History..........................................................................................11 + +## Tables + +#### Table 1. Acronyms.......................................................................................................................... 3 + +#### Table 2. URI Record Contents........................................................................................................ 5 + +#### Table 3. Abbreviation Table............................................................................................................ 5 + +#### Table 4. Simple URL with No Substitution.................................................................................... 9 + +#### Table 5. Storing a Telephone Number............................................................................................ 9 + +#### Table 6. Storing a Proprietary URI on the Tag.............................................................................. 10 + +#### Table 7. Revision History.............................................................................................................. 11 + +URI Record Type Definition Page i + + +Overview + +## 1 Overview + +The URI Service RTD (Record Type Description) is an NFC RTD describing a record to be used +with the NFC Data Exchange Format (NDEF) to retrieve a URI stored in a NFC-compliant tag or +to transport a URI from one NFC device to another. + +The URI (either a URN or URL) also provides a way to store URIs inside other NFC elements, +such as a Smart Poster (please see the Smart Poster RTD for more information). + +### 1.1 Objectives + +The RTD defines the use of NDEF by the means of the NDEF records mapping. + +### 1.2 Purpose + +#### 1.2.1 Mission Statement and Goals + +The purpose of the URI RTD is to provide a “primitive” to contain URIs as defined by RFC 3986 +in a compact manner. + +### 1.3 References + +[NDEF] “NFC Data Exchange Format Specification”, NFC Forum, 2006. + +[NFC RTD] “NFC Record Type Definition (RTD) Specification”, NFC Forum, 2006. + +[RFC 2119] S. Bradner, “Key words for use in RFCs to Indicate Requirement +Levels”, RFC 2119, Harvard University, March 1997. +[http://www.apps.ietf.org/rfc/rfc2119.html](http://www.apps.ietf.org/rfc/rfc2119.html) + +[RFC 3492] A. Costello: “Punycode: A Bootstring encoding of Unicode for +Internationalized Domain Names in Applications (IDNA)”, RFC 3492, +March 2003. [http://www.apps.ietf.org/rfc/rfc3492.html](http://www.apps.ietf.org/rfc/rfc3492.html) + +[RFC 3986] T. Berners-Lee, R. Fielding, L. Masinter, “Uniform Resource Identifiers +(URI): Generic Syntax”, RFC 3986, MIT/LCS, U.C. Irvine, Xerox +Corporation, January 2005. [http://www.apps.ietf.org/rfc/rfc3986.html](http://www.apps.ietf.org/rfc/rfc3986.html) + +[RFC 3987] M. Duerst, M. Suignard, “Internationalized Resource Identifiers (IRIs)”, +RFC 3987, Microsoft Corporation, January 2005. +[http://rfc.net/rfc3987.html](http://rfc.net/rfc3987.html) + +[SMARTPOSTER] “Smart Poster RTD Specification”, NFC Forum, 2006. + +[URI SCHEME] List of Uniform Resource Identifier (URI) schemes registered by IANA. +[http://www.iana.org/assignments/uri-schemes](http://www.iana.org/assignments/uri-schemes) + +### 1.4 Administration + +The URI RTD Specification is an open specification supported by the Near Field Communication +Forum, Inc., located at: + +401 Edgewater Place, Suite 600 +Wakefield, MA, 01880 + +Tel.: +1 781-876-8955 +Fax: +1 781-224-1239 + +URI Record Type Definition Page 1 + + +Overview + +[http://www.nfc-forum.org](http://www.nfc-forum.org) + +The Reference Applications technical working group maintains this specification. + +This specification has been contributed to by Sony, Panasonic, Philips and Nokia. + +### 1.5 Special Word Usage + +The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, +“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this +document are to be interpreted as described in RFC 2119. + +### 1.6 Name and Logo Usage + +The Near Field Communication Forum’s policy regarding the use of the trademarks NFC Forum +and the NFC Forum logo is as follows: + +- Any company MAY claim compatibility with NFC Forum specifications, whether a member + of the NFC Forum or not. +- Permission to use the NFC Forum logos is automatically granted to designated members only + as stipulated on the most recent Membership Privileges document, during the period of time + for which their membership dues are paid. +- Member’s distributors and sales representatives MAY use the NFC Forum logo in promoting + member’s products sold under the name of the member. +- The logo SHALL be printed in black or in color as illustrated on the Logo Page that is + available from the NFC Forum at the address above. The aspect ratio of the logo SHALL be + maintained, but the size MAY be varied. Nothing MAY be added to or deleted from the + logos. +- Since the NFC Forum name is a trademark of the Near Field Communication Forum, the + following statement SHALL be included in all published literature and advertising material in + which the name or logo appears: + NFC Forum and the NFC Forum logo are trademarks of the Near Field Communication + Forum. + +### 1.7 Intellectual Property + +The URI Record Type Definition Specification conforms to the Intellectual Property guidelines +specified in the NFC Forum's Intellectual Property Right Policy, as approved on November 9, +2004 and outlined in the NFC Forum Rules of Procedures, as approved on December 17, 2004. + +URI Record Type Definition Page 2 + + +Overview + +### 1.8 Acronyms + +This table defines all relevant terms and acronyms used in this specification. + +``` +Table 1. Acronyms +``` +Acronyms Definition + +NDEF NFC Data Exchange Format + +URI Uniform Resource Identifier + +URL Uniform Resource Locator (this is a special case of an URI) + +RFU Reserved for Future Use + +NFC Near Field Communication + +URI Record Type Definition Page 3 + + +URI Service + +## 2 URI Service + +This document defines URI Service with data model, describing the application scenarios for +simple Smart Poster applications, the structure of an URI located on an NFC compliant device or +tag, and provides examples. + +The URI record type MAY also be used as a part of some other RTD, in which case it implies no +specific action. A typical example of this might be a case where the developer wants to build his +own record type containing multiple URLs. In this case, it is impossible to divine the meaning of +each URL automatically, so it is left to the handler taking care of the developer’s own type. + +Devices are NOT required to implement any particular URI protocol. + +### 2.1 NDEF Message Sequences + +There are no specific message sequences. + +### 2.2 Dependencies + +The Smart Poster RTD [SMARTPOSTER] may be considered to be an extended version of the +URI RTD. It uses auxiliary records to add metadata to the URI. + +URI Record Type Definition Page 4 + + +NDEF Structure + +## 3 NDEF Structure + +### 3.1 Messaging Sequence + +There is no particular messaging sequence. + +### 3.2 Records Mapping + +#### 3.2.1 URI Record Type + +The Well Known Type for an URI record is “U” (0x55 in the NDEF binary representation). + +The structure of an URI record is described below. + +``` +Table 2. URI Record Contents +``` +``` +Name Offset Size Value Description +Identifier +code +``` +``` +0 1 byte URI identifier code The URI identifier code, as +specified in Table 3. +URI +field +``` +``` +1 N UTF-8 string The rest of the URI, or the entire +URI (if identifier code is 0x00). +``` +#### 3.2.2 URI Identifier Code + +In order to shorten the URI, the first byte of the record data describes the protocol field of an +URI. The following table MUST be used to encode and decode the URI, though applications +MAY use the 0x00 value to denote no prefixing when encoding, regardless of whether there +actually is a suitable abbreviation code. + +For explanations of the different protocols, please refer to the protocol documentations +themselves. NFC devices are not required to support any particular protocol. + +``` +Table 3. Abbreviation Table +``` +``` +Decimal Hex Protocol +0 0x00 N/A. No prepending is done, and the URI field +contains the unabridged URI. +``` +``` +1 0x01 http://www. +2 0x02 https://www. +3 0x03 http:// +4 0x04 https:// +5 0x05 tel: +6 0x06 mailto: +7 0x07 ftp://anonymous:anonymous@ +``` +``` +8 0x08 ftp://ftp. +9 0x09 ftps:// +``` +URI Record Type Definition Page 5 + + +NDEF Structure + +``` +Decimal Hex Protocol +10 0x0A sftp:// +11 0x0B smb:// +12 0x0C nfs:// +13 0x0D ftp:// +14 0x0E dav:// +15 0x0F news: +``` +``` +16 0x10 telnet:// +17 0x11 imap: +18 0x12 rtsp:// +19 0x13 urn: +20 0x14 pop: +21 0x15 sip: +22 0x16 sips: +``` +``` +23 0x17 tftp: +24 0x18 btspp:// +25 0x19 btl2cap:// +26 0x1A btgoep:// +27 0x1B tcpobex:// +28 0x1C irdaobex:// +29 0x1D file:// +``` +``` +30 0x1E urn:epc:id: +31 0x1F urn:epc:tag: +32 0x20 urn:epc:pat: +33 0x21 urn:epc:raw: +34 0x22 urn:epc: +35 0x23 urn:nfc: +36...255 0x24..0xFF RFU +``` +For example, if the content of this field is 0x02, and the content of the URI field reads as “nfc- +forum.org”, the resulting URI is “https://www.nfc-forum.org”. + +If the content this field is zero (0x00), then NO prepending SHALL be done. + +All fields marked RFU SHALL be treated as if they were value zero (no prepending). A +compliant system MUST NOT produce values that are marked RFU. + +URI Record Type Definition Page 6 + + +NDEF Structure + +#### 3.2.3 URI Field + +This field provides the URI as per RFC 3987 [RFC 3987] (so that it is actually an IRI, or +Internationalized Resource Identifier, but for legacy reasons we use the word URI). This IRI can +be a URL or URN as explained before. The encoding used MUST be UTF-8, unless the URI +scheme specifies some particular encoding. + +The length of the IRI can be calculated by taking the length of the payload, and subtracting 1 for +the protocol abbreviation code byte. This is the length in bytes, not in characters (as UTF-8 +characters can occupy more than one byte). + +URIs are defined only in the 7-bit US-ASCII space. Therefore, a compliant application SHOULD +transform the UTF-8 IRI string to a 7-bit US-ASCII string by changing code points above 127 +into the proper encoding. This coding has been defined in the RFC 3987 [RFC 3987] and IDN +[RFC 3492] documents. For different schemes, the encoding may be different. + +For example, if the URI (after the prepending of the URI type field) contains the following string: +“http://www.hääyö.com/”, it is transformed, as per standard IDN [RFC 3492] rules, into +“http://www.xn--hy-viaa5g.com” before acting on it. Most modern applications already support +this new Internationalized Resource Identifier (IRI) scheme. It is RECOMMENDED that +implementations include support for IRI where display of the URI in human-readable form is +anticipated. + +To clarify: yes, the URI MAY contain UTF-8 characters. However, the Internet cannot handle +them, and therefore the URI needs to be transformed before use. For most devices, this +conversion is handled by the application. + +Any character value within the URI between (and including) 0 and 31 SHALL be recorded as an +error, and the URI record to be discarded. Any invalid UTF-8 sequence SHALL be considered an +error, and the entire URI record SHALL be discarded. + +URI Record Type Definition Page 7 + + +Handling Guideline + +## 4 Handling Guideline + +The URI RTD does not define any specific action that the device is required to perform. This is +left to the implementation. + +Please see the Smart Poster RTD [SMARTPOSTER] for an example on how to use the URI RTD +in your own application. + +URI Record Type Definition Page 8 + + +Examples + +## A. Examples + +These examples omit the MB and ME flags from the URI RTD, and assume the Short Record +format. See the NDEF specification [NDEF] for more information. + +### A.1 Simple URL with No Substitution + +To put the URL [http://www.nfc.com](http://www.nfc.com) on a tag using the NDEF protocol, add the following byte +sequence. Total length: 12 bytes. + +``` +Table 4. Simple URL with No Substitution +``` +``` +Offset Content Explanation +``` +``` +0 0xD1 SR = 1, TNF = 0x01 (NFC Forum Well Known +Type), ME=1, MB=1 +1 0x01 Length of the Record Type (1 byte) +``` +``` +2 0x08 Length of the payload (8 bytes) +``` +``` +3 0x55 The URI record type (“U”) +``` +``` +4 0x01 URI identifier (“http://www.”) +``` +``` +5 0x6e 0x66 0x63 0x2e +0x63 0x6f 0x6d +``` +``` +The string “nfc.com” in UTF-8. +``` +### A.2 Storing a Telephone Number + +To store a telephone number (for example, to make a mobile phone make a call to this number), +use the following byte sequence. The number is ‘+358-9-1234567’. Total length is 17 bytes. + +``` +Table 5. Storing a Telephone Number +``` +``` +Offset Content Explanation +``` +``` +0 0xD1 SR = 1, TNF = 0x01 (NFC Forum Well Known +Type), MB=1, ME=1 +1 0x01 Length of the Record Type (1 byte) +``` +``` +2 0x0D Length of the payload (13 bytes) +``` +``` +3 0x55 The Record Name (“U”) +``` +``` +4 0x05 Abbreviation for “tel:” +``` +``` +5 0x2b 0x33 0x35 0x38 +0x39 0x31 0x32 0x33 +0x34 0x35 0x36 0x37 +``` +``` +The string “+35891234567” in UTF-8. +``` +URI Record Type Definition Page 9 + + +Examples + +### A.3 Storing a Proprietary URI on the Tag + +To store a proprietary URI, you can use the following byte sequence. The URI in this case is +“mms://example.com/download.wmv”. Total length is 35 bytes. + +``` +Table 6. Storing a Proprietary URI on the Tag +``` +``` +Offset Content Explanation +``` +``` +0 0xD1 SR = 1, TNF = 0x01 (NFC Forum Well Known +Type), MB=1, ME=1 +1 0x01 Length of the Record Type (1 byte) +``` +``` +2 0x1F Length of the payload (31 bytes) +``` +``` +3 0x55 The Record Name (“U”) +``` +``` +4 0x00 No abbreviation +``` +``` +5 0x6d 0x6d 0x73 0x3a +0x2f 0x2f 0x65 0x78 +0x61 0x6d 0x70 0x6c +0x65 0x2e 0x63 0x6f +0x6d 0x2f 0x64 0x6f +0x77 0x6e 0x6c 0x6f +0x61 0x64 0x2e 0x77 +0x6d 0x76 +``` +``` +The string +“mms://example.com/download.wmv“. +``` +URI Record Type Definition Page 10 + + +Revision History + +## B. Revision History + +The following table outlines the revision history of the RTD_URI Technical Specification. + +``` +Table 7. Revision History +``` +Document +Name + +``` +Revision and +Release Date +``` +``` +Status Change Notice Supersedes +``` +NFCForum-TS- +RTD_URI_1.0 + +``` +1.0, July 2006 Final None +``` +URI Record Type Definition Page 11 + + diff --git a/docs/specification_ndef.pdf b/docs/specification_ndef.pdf new file mode 100644 index 0000000..1fd82f5 Binary files /dev/null and b/docs/specification_ndef.pdf differ diff --git a/extra_script.py b/extra_script.py new file mode 100644 index 0000000..76cfd7c --- /dev/null +++ b/extra_script.py @@ -0,0 +1,6 @@ +Import("env") + +# Hook in die Build-Prozesse +env.AddPreAction("uploadfs", env.VerboseAction("$PROJECT_DIR/scripts/buildfs.sh", "Building Filesystem Image...")) + +env.AddPreAction("upload", env.VerboseAction("$PROJECT_DIR/scripts/uploadfs.sh", "Uploading Filesystem Image...")) \ No newline at end of file diff --git a/gzip_files.py b/gzip_files.py new file mode 100644 index 0000000..4bb48b2 --- /dev/null +++ b/gzip_files.py @@ -0,0 +1,45 @@ +import gzip +import os +import shutil + +## gzip files + +def compress_file(input_file, output_file): + with open(input_file, 'rb') as f_in: + with gzip.open(output_file, 'wb') as f_out: + f_out.writelines(f_in) + +def copy_file(input_file, output_file): + shutil.copy2(input_file, output_file) + +def should_compress(file): + # Komprimiere nur bestimmte Dateitypen + return file.endswith(('.js', '.png', '.css')) + +def main(source_dir, target_dir): + for root, dirs, files in os.walk(source_dir): + rel_path = os.path.relpath(root, source_dir) + for file in files: + input_file = os.path.join(root, file) + output_file_compressed = os.path.join(target_dir, rel_path, file + '.gz') + output_file_original = os.path.join(target_dir, rel_path, file) + + os.makedirs(os.path.dirname(output_file_compressed), exist_ok=True) + + if should_compress(file): + compress_file(input_file, output_file_compressed) + print(f'Compressed {input_file} to {output_file_compressed}') + else: + copy_file(input_file, output_file_original) + print(f'Copied {input_file} to {output_file_original}') + +def init(): + source_dir = 'html' + target_dir = 'data' + + if os.path.exists(target_dir): + shutil.rmtree(target_dir) + + main(source_dir, target_dir) + +init() \ No newline at end of file diff --git a/html/bambu_filaments.json b/html/bambu_filaments.json new file mode 100644 index 0000000..22f36bd --- /dev/null +++ b/html/bambu_filaments.json @@ -0,0 +1,70 @@ +{ + "GFU99": "Generic TPU", + "GFN99": "Generic PA", + "GFN98": "Generic PA-CF", + "GFA01": "Bambu PLA Matte", + "GFA00": "Bambu PLA Basic", + "GFA09": "Bambu PLA Tough", + "GFA07": "Bambu PLA Marble", + "GFA08": "Bambu PLA Sparkle", + "GFA02": "Bambu PLA Metal", + "GFA05": "Bambu PLA Silk", + "GFS00": "Bambu Support W", + "GFL03": "eSUN PLA+", + "GFL01": "PolyTerra PLA", + "GFL00": "PolyLite PLA", + "GFL99": "Generic PLA", + "GFL96": "Generic PLA Silk", + "GFL98": "Generic PLA-CF", + "GFA50": "Bambu PLA-CF", + "GFS02": "Bambu Support For PLA", + "GFA11": "Bambu PLA Aero", + "GFL04": "Overture PLA", + "GFL05": "Overture Matte PLA", + "GFL95": "Generic PLA High Speed", + "GFA12": "Bambu PLA Glow", + "GFA13": "Bambu PLA Dynamic", + "GFA15": "Bambu PLA Galaxy", + "GFS05": "Bambu Support For PLA/PETG", + "GFU01": "Bambu TPU 95A", + "GFU00": "Bambu TPU 95A HF", + "GFG00": "Bambu PETG Basic", + "GFT01": "Bambu PET-CF", + "GFG99": "Generic PETG", + "GFG98": "Generic PETG-CF", + "GFG50": "Bambu PETG-CF", + "GFG60": "PolyLite PETG", + "GFG01": "Bambu PETG Translucent", + "GFG97": "Generic PCTG", + "GFB00": "Bambu ABS", + "GFB99": "Generic ABS", + "GFB60": "PolyLite ABS", + "GFB50": "Bambu ABS-GF", + "GFC00": "Bambu PC", + "GFC99": "Generic PC", + "GFB98": "Generic ASA", + "GFB01": "Bambu ASA", + "GFB61": "PolyLite ASA", + "GFB02": "Bambu ASA-Aero", + "GFS99": "Generic PVA", + "GFS04": "Bambu PVA", + "GFS01": "Bambu Support G", + "GFN03": "Bambu PA-CF", + "GFN04": "Bambu PAHT-CF", + "GFS03": "Bambu Support For PA/PET", + "GFN05": "Bambu PA6-CF", + "GFN08": "Bambu PA6-GF", + "GFS98": "Generic HIPS", + "GFT98": "Generic PPS-CF", + "GFT97": "Generic PPS", + "GFN97": "Generic PPA-CF", + "GFN96": "Generic PPA-GF", + "GFP99": "Generic PE", + "GFP98": "Generic PE-CF", + "GFP97": "Generic PP", + "GFP96": "Generic PP-CF", + "GFP95": "Generic PP-GF", + "GFR99": "Generic EVA", + "GFR98": "Generic PHA", + "GFS97": "Generic BVOH" +} \ No newline at end of file diff --git a/html/favicon.ico b/html/favicon.ico new file mode 100644 index 0000000..14b7586 Binary files /dev/null and b/html/favicon.ico differ diff --git a/html/header.html b/html/header.html new file mode 100644 index 0000000..c18a426 --- /dev/null +++ b/html/header.html @@ -0,0 +1,51 @@ + + + + + + FilaMan - Filament Management Tool + + + + + + diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..5d9aaef --- /dev/null +++ b/html/index.html @@ -0,0 +1,37 @@ +{{header}} + +
+

FilaMan

+

Filament Management Tool

+

Your smart solution for Filament Management in 3D printing.

+ +

About FilaMan

+

+ FilaMan is a tool designed to simplify filament spool management. It allows you to identify and weigh filament spools, + automatically sync data with the self-hosted Spoolman platform, + and ensure compatibility with OpenSpool for Bambu printers. +

+ +
+
+

Spool Identification

+

Easily identify filament spools using NFC tags (NTag215 or larger).

+
+
+

Automatic Syncing

+

Seamlessly update spool data with Spoolman for accurate tracking.

+
+
+

OpenSpool Compatibility

+

Works with OpenSpool to recognize and activate spools on Bambu printers.

+
+
+ +

Future Plans

+

+ We are working on expanding compatibility to support smaller NFC tags like NTag213 + and developing custom software to enhance the OpenSpool experience. +

+
+ + diff --git a/html/logo.png b/html/logo.png new file mode 100644 index 0000000..c4adb61 Binary files /dev/null and b/html/logo.png differ diff --git a/html/rfid.html b/html/rfid.html new file mode 100644 index 0000000..ec34f37 --- /dev/null +++ b/html/rfid.html @@ -0,0 +1,114 @@ +{{header}} + +
+
+ +
+
+
+

Statistics

+ +
+
+

Spools

+
+ total: + +
+ without Tag: + +
+
+ +
+
+

Overview

+
    +
  • + Manufacturer: + +
  • +
  • + Weight: + kg +
  • +
  • + Length: + m +
  • +
+
+
+

Materials

+
    + +
+
+
+
+
+
+

NFC-Tag

+ +
+
+
+
+ + +
+
+

Spoolman Spools

+

1. select Manufacturer

+ +
+ + +
+
+ + +
+ + +
+
+

Bambu AMS

+
+
Wait for AMS-Data...
+
+
+
+
+
+ + + + + + diff --git a/html/rfid.js b/html/rfid.js new file mode 100644 index 0000000..675b2c4 --- /dev/null +++ b/html/rfid.js @@ -0,0 +1,570 @@ +// WebSocket Variablen +let socket; +let isConnected = false; +const RECONNECT_INTERVAL = 5000; +const HEARTBEAT_INTERVAL = 10000; +let heartbeatTimer = null; +let lastHeartbeatResponse = Date.now(); +const HEARTBEAT_TIMEOUT = 20000; +let reconnectTimer = null; + +// WebSocket Funktionen +function startHeartbeat() { + if (heartbeatTimer) clearInterval(heartbeatTimer); + + heartbeatTimer = setInterval(() => { + // Prüfe ob zu lange keine Antwort kam + if (Date.now() - lastHeartbeatResponse > HEARTBEAT_TIMEOUT) { + isConnected = false; + updateConnectionStatus(); + if (socket) { + socket.close(); + socket = null; + } + return; + } + + if (!socket || socket.readyState !== WebSocket.OPEN) { + isConnected = false; + updateConnectionStatus(); + return; + } + + try { + // Sende Heartbeat + socket.send(JSON.stringify({ type: 'heartbeat' })); + } catch (error) { + isConnected = false; + updateConnectionStatus(); + if (socket) { + socket.close(); + socket = null; + } + } + }, HEARTBEAT_INTERVAL); +} + +function initWebSocket() { + // Clear any existing reconnect timer + if (reconnectTimer) { + clearTimeout(reconnectTimer); + reconnectTimer = null; + } + + // Wenn eine existierende Verbindung besteht, diese erst schließen + if (socket) { + socket.close(); + socket = null; + } + + try { + socket = new WebSocket('ws://' + window.location.host + '/ws'); + + socket.onopen = function() { + isConnected = true; + updateConnectionStatus(); + startHeartbeat(); // Starte Heartbeat nach erfolgreicher Verbindung + }; + + socket.onclose = function() { + isConnected = false; + updateConnectionStatus(); + if (heartbeatTimer) clearInterval(heartbeatTimer); + + // Nur neue Verbindung versuchen, wenn kein Timer läuft + if (!reconnectTimer) { + reconnectTimer = setTimeout(() => { + initWebSocket(); + }, RECONNECT_INTERVAL); + } + }; + + socket.onerror = function(error) { + isConnected = false; + updateConnectionStatus(); + if (heartbeatTimer) clearInterval(heartbeatTimer); + + // Bei Fehler Verbindung schließen und neu aufbauen + if (socket) { + socket.close(); + socket = null; + } + }; + + socket.onmessage = function(event) { + lastHeartbeatResponse = Date.now(); // Aktualisiere Zeitstempel bei jeder Server-Antwort + + const data = JSON.parse(event.data); + if (data.type === 'amsData') { + displayAmsData(data.payload); + } else if (data.type === 'nfcTag') { + updateNfcStatusIndicator(data.payload); + } else if (data.type === 'nfcData') { + updateNfcData(data.payload); + } else if (data.type === 'writeNfcTag') { + handleWriteNfcTagResponse(data.success); + } else if (data.type === 'heartbeat') { + // Optional: Spezifische Behandlung von Heartbeat-Antworten + // Update status dots + const bambuDot = document.getElementById('bambuDot'); + const spoolmanDot = document.getElementById('spoolmanDot'); + const ramStatus = document.getElementById('ramStatus'); + + if (bambuDot) { + bambuDot.className = 'status-dot ' + (data.bambu_connected ? 'online' : 'offline'); + } + if (spoolmanDot) { + spoolmanDot.className = 'status-dot ' + (data.spoolman_connected ? 'online' : 'offline'); + } + if (ramStatus) { + ramStatus.textContent = `${data.freeHeap}k`; + } + } + }; + } catch (error) { + isConnected = false; + updateConnectionStatus(); + + // Nur neue Verbindung versuchen, wenn kein Timer läuft + if (!reconnectTimer) { + reconnectTimer = setTimeout(() => { + initWebSocket(); + }, RECONNECT_INTERVAL); + } + } +} + +function updateConnectionStatus() { + const statusElement = document.querySelector('.connection-status'); + if (!isConnected) { + statusElement.classList.remove('hidden'); + // Verzögerung hinzufügen, damit die CSS-Transition wirken kann + setTimeout(() => { + statusElement.classList.add('visible'); + }, 10); + } else { + statusElement.classList.remove('visible'); + // Warte auf das Ende der Fade-out Animation bevor wir hidden setzen + setTimeout(() => { + statusElement.classList.add('hidden'); + }, 300); + } +} + +// Event Listeners +document.addEventListener("DOMContentLoaded", function() { + initWebSocket(); + + // Event Listener für Checkbox + document.getElementById("onlyWithoutSmId").addEventListener("change", function() { + const spoolsData = window.getSpoolData(); + window.populateVendorDropdown(spoolsData); + }); +}); + +// Event Listener für Spoolman Events +document.addEventListener('spoolDataLoaded', function(event) { + window.populateVendorDropdown(event.detail); +}); + +document.addEventListener('spoolmanError', function(event) { + showNotification(`Spoolman Error: ${event.detail.message}`, false); +}); + +document.addEventListener('filamentSelected', function(event) { + updateNfcInfo(); + // Zeige Spool-Buttons wenn ein Filament ausgewählt wurde + const selectedText = document.getElementById("selected-filament").textContent; + updateSpoolButtons(selectedText !== "Please choose..."); +}); + +// Hilfsfunktion für kontrastreiche Textfarbe +function getContrastColor(hexcolor) { + // Konvertiere Hex zu RGB + const r = parseInt(hexcolor.substr(0,2),16); + const g = parseInt(hexcolor.substr(2,2),16); + const b = parseInt(hexcolor.substr(4,2),16); + + // Berechne Helligkeit (YIQ Formel) + const yiq = ((r*299)+(g*587)+(b*114))/1000; + + // Return schwarz oder weiß basierend auf Helligkeit + return (yiq >= 128) ? '#000000' : '#FFFFFF'; +} + +function updateNfcInfo() { + const selectedText = document.getElementById("selected-filament").textContent; + const nfcInfo = document.getElementById("nfcInfo"); + const writeButton = document.getElementById("writeNfcButton"); + + if (selectedText === "Please choose...") { + nfcInfo.textContent = "No Filament selected"; + nfcInfo.classList.remove("nfc-success", "nfc-error"); + writeButton.classList.add("hidden"); + return; + } + + // Finde die ausgewählte Spule in den Daten + const selectedSpool = spoolsData.find(spool => + `${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText + ); + + if (selectedSpool && selectedSpool.extra.nfc_id) { + nfcInfo.textContent = "NFC Tag assigned"; + nfcInfo.classList.add("nfc-success"); + nfcInfo.classList.remove("nfc-error"); + } else { + nfcInfo.textContent = "No NFC-Tag assigned"; + nfcInfo.classList.add("nfc-error"); + nfcInfo.classList.remove("nfc-success"); + } + + if (selectedSpool) { + writeButton.classList.remove("hidden"); + } else { + writeButton.classList.add("hidden"); + } +} + +function displayAmsData(amsData) { + const amsDataContainer = document.getElementById('amsData'); + amsDataContainer.innerHTML = ''; + + amsData.forEach((ams) => { + // Bestimme den Anzeigenamen für das AMS + const amsDisplayName = ams.ams_id === 255 ? 'External Spool' : `AMS ${ams.ams_id}`; + + const trayHTML = ams.tray.map(tray => { + // Prüfe ob überhaupt Daten vorhanden sind + const relevantFields = ['tray_type', 'tray_sub_brands', 'tray_info_idx', 'setting_id']; + const hasAnyContent = relevantFields.some(field => + tray[field] !== null && + tray[field] !== undefined && + tray[field] !== '' && + tray[field] !== 'null' + ); + + if (!hasAnyContent) { + return ` +
+

Tray ${tray.id}

+

Empty

+
+
`; + } + + // Nur für nicht-leere Trays den Button-HTML erstellen + const buttonHtml = ` + `; + + // Generiere den Type mit Color-Box zusammen + const typeWithColor = tray.tray_type ? + `

Typ: ${tray.tray_type} ${tray.tray_color ? `` : ''}

` : ''; + + // Array mit restlichen Tray-Eigenschaften + const trayProperties = [ + { key: 'tray_sub_brands', label: 'Sub Brands' }, + { key: 'tray_info_idx', label: 'Filament Index' }, + { key: 'setting_id', label: 'Setting ID' } + ]; + + // Nur gültige Felder anzeigen + const trayDetails = trayProperties + .filter(prop => + tray[prop.key] !== null && + tray[prop.key] !== undefined && + tray[prop.key] !== '' && + tray[prop.key] !== 'null' + ) + .map(prop => `

${prop.label}: ${tray[prop.key]}

`) + .join(''); + + // Temperaturen nur anzeigen, wenn beide nicht 0 sind + const tempHTML = (tray.nozzle_temp_min > 0 && tray.nozzle_temp_max > 0) + ? `

Nozzle Temp: ${tray.nozzle_temp_min}°C - ${tray.nozzle_temp_max}°C

` + : ''; + + // Bestimme den Anzeigenamen für das Tray + const trayDisplayName = (ams.ams_id === 255) ? 'External' : `Tray ${tray.id}`; + + return ` +
+
+ ${buttonHtml} +

${trayDisplayName}

+ ${typeWithColor} + ${trayDetails} + ${tempHTML} +
+
+
`; + }).join(''); + + const amsInfo = ` +
+

${amsDisplayName}:

+
+ ${trayHTML} +
+
`; + + amsDataContainer.innerHTML += amsInfo; + }); +} + +// Neue Funktion zum Anzeigen/Ausblenden der Spool-Buttons +function updateSpoolButtons(show) { + const spoolButtons = document.querySelectorAll('.spool-button'); + spoolButtons.forEach(button => { + button.style.display = show ? 'block' : 'none'; + }); +} + +// Neue Funktion zum Behandeln des Spool-In-Klicks +function handleSpoolIn(amsId, trayId) { + // Prüfe WebSocket Verbindung zuerst + if (!socket || socket.readyState !== WebSocket.OPEN) { + showNotification("No active WebSocket connection!", false); + console.error("WebSocket not connected"); + return; + } + + // Hole das ausgewählte Filament + const selectedText = document.getElementById("selected-filament").textContent; + if (selectedText === "Please choose...") { + showNotification("Choose Filament first", false); + return; + } + + // Finde die ausgewählte Spule in den Daten + const selectedSpool = spoolsData.find(spool => + `${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText + ); + + if (!selectedSpool) { + showNotification("Selected Spool not found", false); + return; + } + + // Temperaturwerte extrahieren + let minTemp = "175"; + let maxTemp = "275"; + + if (Array.isArray(selectedSpool.filament.nozzle_temperature) && + selectedSpool.filament.nozzle_temperature.length >= 2) { + minTemp = selectedSpool.filament.nozzle_temperature[0]; + maxTemp = selectedSpool.filament.nozzle_temperature[1]; + } + + // Erstelle Payload + const payload = { + type: 'setBambuSpool', + payload: { + amsId: amsId, + trayId: trayId, + color: selectedSpool.filament.color_hex || "FFFFFF", + nozzle_temp_min: parseInt(minTemp), + nozzle_temp_max: parseInt(maxTemp), + type: selectedSpool.filament.material, + brand: selectedSpool.filament.vendor.name + } + }; + + // Debug logging + console.log("Sende WebSocket Nachricht:", payload); + + try { + socket.send(JSON.stringify(payload)); + showNotification(`Spool set in AMS ${amsId} Tray ${trayId}. Pls wait`, true); + } catch (error) { + console.error("Fehler beim Senden der WebSocket Nachricht:", error); + showNotification("Fehler beim Senden der Daten", false); + } +} + +function updateNfcStatusIndicator(data) { + const indicator = document.getElementById('nfcStatusIndicator'); + + if (data.found === 0) { + // Kein NFC Tag gefunden + indicator.className = 'status-circle'; + } else if (data.found === 1) { + // NFC Tag erfolgreich gelesen + indicator.className = 'status-circle success'; + } else { + // Fehler beim Lesen + indicator.className = 'status-circle error'; + } +} + +function updateNfcData(data) { + // Den Container für den NFC Status finden + const nfcStatusContainer = document.querySelector('.nfc-status-display'); + + // Bestehende Daten-Anzeige entfernen falls vorhanden + const existingData = nfcStatusContainer.querySelector('.nfc-data'); + if (existingData) { + existingData.remove(); + } + + // Neues div für die Datenanzeige erstellen + const nfcDataDiv = document.createElement('div'); + nfcDataDiv.className = 'nfc-data'; + + // Wenn ein Fehler vorliegt oder keine Daten vorhanden sind + if (data.error || data.info || !data || Object.keys(data).length === 0) { + // Zeige Fehlermeldung oder leere Nachricht + if (data.error || data.info) { + if (data.error) { + nfcDataDiv.innerHTML = ` +
+

Error: ${data.error}

+
`; + } else { + nfcDataDiv.innerHTML = ` +
+

Info: ${data.info}

+
`; + } + + } else { + nfcDataDiv.innerHTML = '
'; + } + nfcStatusContainer.appendChild(nfcDataDiv); + return; + } + + // HTML für die Datenanzeige erstellen + let html = ` +
+

Brand: ${data.brand || 'N/A'}

+

Type: ${data.type || 'N/A'} ${data.color_hex ? `` : ''}

+ `; + + // Spoolman ID anzeigen + html += `

Spoolman ID: ${data.sm_id || 'No Spoolman ID'}

`; + + // Nur wenn eine sm_id vorhanden ist, aktualisiere die Dropdowns + if (data.sm_id) { + const matchingSpool = spoolsData.find(spool => spool.id === parseInt(data.sm_id)); + if (matchingSpool) { + // Zuerst Hersteller-Dropdown aktualisieren + document.getElementById("vendorSelect").value = matchingSpool.filament.vendor.id; + + // Dann Filament-Dropdown aktualisieren und Spule auswählen + updateFilamentDropdown(); + setTimeout(() => { + // Warte kurz bis das Dropdown aktualisiert wurde + selectFilament(matchingSpool); + }, 100); + } + } + + html += '
'; + nfcDataDiv.innerHTML = html; + + + // Neues div zum Container hinzufügen + nfcStatusContainer.appendChild(nfcDataDiv); +} + +function writeNfcTag() { + const selectedText = document.getElementById("selected-filament").textContent; + if (selectedText === "Please choose...") { + alert('Please select a Spool first.'); + return; + } + + const spoolsData = window.getSpoolData(); + const selectedSpool = spoolsData.find(spool => + `${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText + ); + + if (!selectedSpool) { + alert('Ausgewählte Spule konnte nicht gefunden werden.'); + return; + } + + // Temperaturwerte korrekt extrahieren + let minTemp = "175"; + let maxTemp = "275"; + + if (Array.isArray(selectedSpool.filament.nozzle_temperature) && + selectedSpool.filament.nozzle_temperature.length >= 2) { + minTemp = String(selectedSpool.filament.nozzle_temperature[0]); + maxTemp = String(selectedSpool.filament.nozzle_temperature[1]); + } + + // Erstelle das NFC-Datenpaket mit korrekten Datentypen + const nfcData = { + version: "2.0", + protocol: "openspool", + color_hex: selectedSpool.filament.color_hex || "FFFFFF", + type: selectedSpool.filament.material, + min_temp: minTemp, + max_temp: maxTemp, + brand: selectedSpool.filament.vendor.name, + sm_id: String(selectedSpool.id) // Konvertiere zu String + }; + + if (socket?.readyState === WebSocket.OPEN) { + const writeButton = document.getElementById("writeNfcButton"); + writeButton.classList.add("writing"); + writeButton.textContent = "Writing"; + socket.send(JSON.stringify({ + type: 'writeNfcTag', + payload: nfcData + })); + } else { + alert('Not connected to Server. Please check connection.'); + } +} + +function handleWriteNfcTagResponse(success) { + const writeButton = document.getElementById("writeNfcButton"); + writeButton.classList.remove("writing"); + writeButton.classList.add(success ? "success" : "error"); + writeButton.textContent = success ? "Write success" : "Write failed"; + + setTimeout(() => { + writeButton.classList.remove("success", "error"); + writeButton.textContent = "Write Tag"; + }, 5000); +} + +function showNotification(message, isSuccess) { + const notification = document.createElement('div'); + notification.className = `notification ${isSuccess ? 'success' : 'error'}`; + notification.textContent = message; + document.body.appendChild(notification); + + // Nach 3 Sekunden ausblenden + setTimeout(() => { + notification.classList.add('fade-out'); + setTimeout(() => { + notification.remove(); + }, 300); + }, 3000); +} \ No newline at end of file diff --git a/html/spool_in.png b/html/spool_in.png new file mode 100644 index 0000000..77821a8 Binary files /dev/null and b/html/spool_in.png differ diff --git a/html/spoolman.html b/html/spoolman.html new file mode 100644 index 0000000..3faf19a --- /dev/null +++ b/html/spoolman.html @@ -0,0 +1,80 @@ +{{header}} + + + +
+

Spoolman API URL / Bambu Credentials

+ + + +

+ +

Bambu Lab Printer Credentials

+
+
+ + +
+
+ + +
+
+ + +
+ +

+
+
+ + diff --git a/html/spoolman.js b/html/spoolman.js new file mode 100644 index 0000000..54be9b5 --- /dev/null +++ b/html/spoolman.js @@ -0,0 +1,308 @@ +// Globale Variablen +let spoolmanUrl = ''; +let spoolsData = []; + +// Hilfsfunktionen für Datenmanipulation +function processSpoolData(data) { + return data.map(spool => ({ + id: spool.id, + remaining_weight: spool.remaining_weight, + remaining_length: spool.remaining_length, + filament: spool.filament, + extra: spool.extra + })); +} + +// Dropdown-Funktionen +function populateVendorDropdown(data, selectedSmId = null) { + const vendorSelect = document.getElementById("vendorSelect"); + if (!vendorSelect) { + console.error('vendorSelect Element nicht gefunden'); + return; + } + const onlyWithoutSmId = document.getElementById("onlyWithoutSmId"); + if (!onlyWithoutSmId) { + console.error('onlyWithoutSmId Element nicht gefunden'); + return; + } + + // Separate Objekte für alle Hersteller und gefilterte Hersteller + const allVendors = {}; + const filteredVendors = {}; + + vendorSelect.innerHTML = ''; + + let vendorIdToSelect = null; + let totalSpools = 0; + let spoolsWithoutTag = 0; + let totalWeight = 0; + let totalLength = 0; + // Neues Objekt für Material-Gruppierung + const materials = {}; + + data.forEach(spool => { + if (!spool.filament || !spool.filament.vendor) { + return; + } + + totalSpools++; + + // Material zählen und gruppieren + if (spool.filament.material) { + const material = spool.filament.material.toUpperCase(); // Normalisierung + materials[material] = (materials[material] || 0) + 1; + } + + // Addiere Gewicht und Länge + if (spool.remaining_weight) { + totalWeight += spool.remaining_weight; + } + if (spool.remaining_length) { + totalLength += spool.remaining_length; + } + + console.log("Länge gesamt: " + spool.remaining_length); + console.log("Gewicht gesamt" + spool.remaining_weight); + + const vendor = spool.filament.vendor; + + const hasValidNfcId = spool.extra && + spool.extra.nfc_id && + spool.extra.nfc_id !== '""' && + spool.extra.nfc_id !== '"\\"\\"\\""'; + + if (!hasValidNfcId) { + spoolsWithoutTag++; + } + + // Alle Hersteller sammeln + if (!allVendors[vendor.id]) { + allVendors[vendor.id] = vendor.name; + } + + // Gefilterte Hersteller für Dropdown + if (!filteredVendors[vendor.id]) { + if (!onlyWithoutSmId.checked || !hasValidNfcId) { + filteredVendors[vendor.id] = vendor.name; + } + } + }); + + // Dropdown mit gefilterten Herstellern befüllen + Object.entries(filteredVendors).forEach(([id, name]) => { + const option = document.createElement("option"); + option.value = id; + option.textContent = name; + vendorSelect.appendChild(option); + }); + + document.getElementById("totalSpools").textContent = totalSpools; + document.getElementById("spoolsWithoutTag").textContent = spoolsWithoutTag; + // Zeige die Gesamtzahl aller Hersteller an + document.getElementById("totalVendors").textContent = Object.keys(allVendors).length; + + // Neue Statistiken hinzufügen + document.getElementById("totalWeight").textContent = (totalWeight / 1000).toFixed(2); + document.getElementById("totalLength").textContent = (totalLength / 1000).toFixed(2); + + // Material-Statistiken zum DOM hinzufügen + const materialsList = document.getElementById("materialsList"); + materialsList.innerHTML = ''; + Object.entries(materials) + .sort(([,a], [,b]) => b - a) // Sortiere nach Anzahl absteigend + .forEach(([material, count]) => { + const li = document.createElement("li"); + li.textContent = `${material}: ${count} ${count === 1 ? 'Spule' : 'Spulen'}`; + materialsList.appendChild(li); + }); + + if (vendorIdToSelect) { + vendorSelect.value = vendorIdToSelect; + updateFilamentDropdown(selectedSmId); + } +} + +function updateFilamentDropdown(selectedSmId = null) { + const vendorId = document.getElementById("vendorSelect").value; + const dropdownContentInner = document.getElementById("filament-dropdown-content"); + const filamentSection = document.getElementById("filamentSection"); + const onlyWithoutSmId = document.getElementById("onlyWithoutSmId").checked; + const selectedText = document.getElementById("selected-filament"); + const selectedColor = document.getElementById("selected-color"); + + dropdownContentInner.innerHTML = ''; + selectedText.textContent = "Bitte wählen..."; + selectedColor.style.backgroundColor = '#FFFFFF'; + + if (vendorId) { + const filteredFilaments = spoolsData.filter(spool => { + const hasValidNfcId = spool.extra && + spool.extra.nfc_id && + spool.extra.nfc_id !== '""' && + spool.extra.nfc_id !== '"\\"\\"\\""'; + + return spool.filament.vendor.id == vendorId && + (!onlyWithoutSmId || !hasValidNfcId); + }); + + filteredFilaments.forEach(spool => { + const option = document.createElement("div"); + option.className = "dropdown-option"; + option.setAttribute("data-value", spool.filament.id); + option.setAttribute("data-nfc-id", spool.extra.nfc_id || ""); + + const colorHex = spool.filament.color_hex || 'FFFFFF'; + option.innerHTML = ` +
+ ${spool.id} | ${spool.filament.name} (${spool.filament.material}) + `; + + option.onclick = () => selectFilament(spool); + dropdownContentInner.appendChild(option); + }); + + filamentSection.classList.remove("hidden"); + } else { + filamentSection.classList.add("hidden"); + } +} + +function selectFilament(spool) { + const selectedColor = document.getElementById("selected-color"); + const selectedText = document.getElementById("selected-filament"); + const dropdownContent = document.getElementById("filament-dropdown-content"); + + selectedColor.style.backgroundColor = `#${spool.filament.color_hex || 'FFFFFF'}`; + selectedText.textContent = `${spool.id} | ${spool.filament.name} (${spool.filament.material})`; + dropdownContent.classList.remove("show"); + + document.dispatchEvent(new CustomEvent('filamentSelected', { + detail: spool + })); +} + +// Initialisierung und Event-Handler +async function initSpoolman() { + try { + const response = await fetch('/api/url'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + if (!data.spoolman_url) { + throw new Error('spoolman_url nicht in der Antwort gefunden'); + } + + spoolmanUrl = data.spoolman_url; + + const fetchedData = await fetchSpoolData(); + spoolsData = processSpoolData(fetchedData); + + document.dispatchEvent(new CustomEvent('spoolDataLoaded', { + detail: spoolsData + })); + } catch (error) { + console.error('Fehler beim Initialisieren von Spoolman:', error); + document.dispatchEvent(new CustomEvent('spoolmanError', { + detail: { message: error.message } + })); + } +} + +async function fetchSpoolData() { + try { + if (!spoolmanUrl) { + throw new Error('Spoolman URL ist nicht initialisiert'); + } + + const response = await fetch(`${spoolmanUrl}/api/v1/spool`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error('Fehler beim Abrufen der Spulen-Daten:', error); + return []; + } +} + +/* +// Exportiere Funktionen +window.getSpoolData = () => spoolsData; +window.reloadSpoolData = initSpoolman; +window.populateVendorDropdown = populateVendorDropdown; +window.updateFilamentDropdown = updateFilamentDropdown; +window.toggleFilamentDropdown = () => { + const content = document.getElementById("filament-dropdown-content"); + content.classList.toggle("show"); +}; +*/ + +// Event Listener +document.addEventListener('DOMContentLoaded', () => { + initSpoolman(); + + const vendorSelect = document.getElementById('vendorSelect'); + if (vendorSelect) { + vendorSelect.addEventListener('change', () => updateFilamentDropdown()); + } + + const onlyWithoutSmId = document.getElementById('onlyWithoutSmId'); + if (onlyWithoutSmId) { + onlyWithoutSmId.addEventListener('change', () => { + populateVendorDropdown(spoolsData); + updateFilamentDropdown(); + }); + } + + document.addEventListener('spoolDataLoaded', (event) => { + populateVendorDropdown(event.detail); + }); + + window.onclick = function(event) { + if (!event.target.closest('.custom-dropdown')) { + const dropdowns = document.getElementsByClassName("dropdown-content"); + for (let dropdown of dropdowns) { + dropdown.classList.remove("show"); + } + } + }; + + const refreshButton = document.getElementById('refreshSpoolman'); + if (refreshButton) { + refreshButton.addEventListener('click', async () => { + try { + refreshButton.disabled = true; + refreshButton.textContent = 'Wird aktualisiert...'; + await initSpoolman(); + refreshButton.textContent = 'Refresh Spoolman'; + } finally { + refreshButton.disabled = false; + } + }); + } +}); + +// Exportiere Funktionen +window.getSpoolData = () => spoolsData; +window.setSpoolData = (data) => { spoolsData = data; }; +window.reloadSpoolData = initSpoolman; +window.populateVendorDropdown = populateVendorDropdown; +window.updateFilamentDropdown = updateFilamentDropdown; +window.toggleFilamentDropdown = () => { + const content = document.getElementById("filament-dropdown-content"); + content.classList.toggle("show"); +}; + +// Event Listener für Click außerhalb Dropdown +window.onclick = function(event) { + if (!event.target.closest('.custom-dropdown')) { + const dropdowns = document.getElementsByClassName("dropdown-content"); + for (let dropdown of dropdowns) { + dropdown.classList.remove("show"); + } + } +}; \ No newline at end of file diff --git a/html/style.css b/html/style.css new file mode 100644 index 0000000..11d35c8 --- /dev/null +++ b/html/style.css @@ -0,0 +1,901 @@ +/* Allgemeine Stile */ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f8f9fa; + color: #333; + display: flex; + flex-direction: column; + align-items: center; + min-height: 100vh; + text-align: center; +} + +.logo { + height: 40px; /* Anpassen an die Navbar-Höhe */ + width: auto; + margin-right: 15px; + margin-left: 10px; +} + +/* Navigationsleiste */ +.navbar { + background-color: #007bff; + width: 100%; + display: flex; + justify-content: center; /* Zentriert die Navigation */ + padding: 10px 0; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + position: fixed; + top: 0; + left: 0; + z-index: 1000; +} + +.navbar a { + display: inline-block; + color: white; + text-align: center; + padding: 14px 20px; + text-decoration: none; + font-weight: bold; + transition: background 0.3s, color 0.3s; + cursor: pointer !important; /* Wichtig: cursor-Definition für Nav-Links */ +} + +.navbar a:hover { + background-color: #0056b3; + color: #fff; + cursor: pointer !important; +} + +/* Inhalt */ +.container { + padding: 20px; + width: 100%; + max-width: none; + background: white; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + margin-top: 20px; /* Platz für die fixe Navbar */ +} + +/* Überschriften */ +h1 { + color: #007bff; + text-align: center; +} + +h3 { + color: #007bff; + font-size: 24px; + margin-top: 5px; + margin-bottom: 5px; + font-weight: bold; +} + +/* Formulare */ +form { + display: flex; + flex-direction: column; + gap: 10px; + padding: 20px; +} + +label { + font-weight: bold; +} + +input[type="text"], input[type="submit"] { + padding: 10px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 16px; +} + +input[type="text"]:focus { + border-color: #007bff; + outline: none; +} + +input[type="submit"] { + background-color: #007bff; + color: white; + border: none; + cursor: pointer; + transition: background 0.3s; +} + +input[type="submit"]:hover { + background-color: #0056b3; +} + +/* Buttons */ +button { + padding: 10px 15px; + border: none; + border-radius: 5px; + background-color: #007bff; + color: white; + font-size: 16px; + cursor: pointer; + transition: background 0.3s; +} + +button:hover { + background-color: #0056b3; +} + +/* Statusnachricht */ +#statusMessage { + margin-top: 10px; + padding: 10px; + border-radius: 5px; + background-color: #8cc4fd; + text-align: center; + font-weight: bold; +} + +.features { + display: flex; + justify-content: space-between; + margin-top: 30px; + text-align: left; +} +.feature { + flex: 1; + padding: 20px; + background-color: #f9f9f9; + border-radius: 8px; + margin: 0 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05); +} +.feature h3 { + font-size: 1.4rem; + margin-bottom: 10px; + color: #007bff; +} +.feature p { + font-size: 1rem; + color: #555; +} + +p { + font-size: 1rem; + color: #555; +} + +a { + color: #007bff; + text-decoration: none; + font-weight: bold; + cursor: pointer; +} +a:hover { + text-decoration: underline; +} + +/* Karten-Stil für optische Trennung */ +.card { + background: #f9f9f9; + padding: 15px; + margin: 20px 0; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Versteckte Elemente */ +.hidden { + display: none; +} + +/* Dropdown-Stil */ +.styled-select { + width: 100%; + padding: 12px 15px; + font-size: 16px; + border: 2px solid #e0e0e0; + border-radius: 8px; + background-color: #fff; + cursor: pointer; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23007bff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 15px center; + background-size: 15px; + transition: all 0.3s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +.styled-select:hover { + border-color: #007bff; + box-shadow: 0 3px 6px rgba(0, 123, 255, 0.1); +} + +.styled-select:focus { + border-color: #007bff; + outline: none; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25); +} + +.styled-select:disabled { + background-color: #f5f5f5; + cursor: not-allowed; + opacity: 0.7; +} + +/* NFC-Status */ +.nfc-status { + font-weight: bold; + margin-top: 10px; +} + +.nfc-success { + color: green; +} + +.nfc-error { + color: red; +} + +/* Füge diese neuen Styles zu deiner style.css hinzu */ + +.three-column-layout { + display: flex; + justify-content: space-between; + gap: 20px; + margin-top: 20px; + width: 100%; +} + +.column { + flex: 1; + min-width: 0; /* Verhindert Überlauf bei flex-Elementen */ +} + +.feature-box { + background: white; + padding: 5px 20px 20px 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; +} + +.feature-box h2 { + color: #007bff; + font-size: 1.4rem; + margin-bottom: 15px; +} + +.feature-box ul { + list-style: none; + padding: 0; + margin: 0; +} + +.feature-box ul li { + padding: 8px 5px 5px 5px; + border-bottom: 1px solid #eee; +} + +.content { + width: 95%; + max-width: 1400px; + margin: 0 auto; + padding-top: 60px; + padding-bottom: 20px;; +} + +.tray { + background: #ffffff; + padding: 15px; + margin: 10px 0; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + border-left: 4px solid #ffffff; +} + +.tray p { + margin: 5px 0; +} + +.tray b { + color: #007bff; +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .three-column-layout { + flex-direction: column; + } + + .column { + width: 100%; + } +} + +.nfc-status-display { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 0; +} + +.status-circle { + width: 20px; + height: 20px; + border-radius: 50%; + display: inline-block; + border: 2px solid #ccc; + background-color: #ffffff; +} + +.status-circle.success { + background-color: #28a745; + border-color: #218838; +} + +.status-circle.error { + background-color: #dc3545; + border-color: #c82333; +} + +.nfc-data { + padding: 10px; + background-color: #f8f9fa; + border-radius: 4px; + margin-top: 5px; + width: 100%; +} + +.nfc-data p { + margin: 5px 0; + font-size: 0.9em; +} + +.nfc-status-display { + display: flex; + flex-direction: column; + gap: 10px; +} + +.error-message { + padding: 10px; + background-color: #fff3f3; + border-radius: 4px; + border-left: 4px solid #dc3545; +} + +.info-message { + padding: 10px; + background-color: #fff3f3; + border-radius: 4px; + border-left: 4px solid #39d82e; +} + +.nfc-header { + display: grid; + grid-template-columns: 40px 1fr 40px; + align-items: center; + margin-bottom: 10px; +} + +.nfc-header h2 { + margin: 0; + grid-column: 2; + text-align: center; +} + +.nfc-header .status-circle { + grid-column: 3; + justify-self: end; +} + +.content-header { + display: flex; + justify-content: center; + align-items: center; + position: relative; +} + +.connection-status { + display: flex; + align-items: center; + gap: 10px; + background-color: #fff3f3; + border: 1px solid #dc3545; + border-radius: 4px; + padding: 10px 15px; + margin: 15px auto; + color: #dc3545; + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + max-width: 90%; + opacity: 0; + transition: opacity 0.3s ease; +} + +.connection-status.visible { + opacity: 1; +} + +.spinner { + flex-shrink: 0; + width: 16px; + height: 16px; + border: 2px solid rgba(220, 53, 69, 0.2); + border-top-color: #dc3545; + border-radius: 50%; +} + +.connection-status.visible .spinner { + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.connection-status.hidden { + display: none; +} + +.nfc-actions { + display: flex; + gap: 10px; + justify-content: center; /* Zentriert das div */ +} + +.btn { + padding: 8px 16px; + border-radius: 4px; + border: none; + cursor: pointer; + font-weight: bold; +} + +.btn-primary { + background-color: #007bff; + color: white; +} + +.btn-primary:hover { + background-color: #0056b3; +} + +.btn-danger { + background-color: #dc3545; + color: white; +} + +.btn-danger:hover { + background-color: #c82333; +} + +/* Filament Select Styling */ +#filamentSelect { + width: 100%; + padding: 12px 15px; + font-size: 16px; + border: 2px solid #e0e0e0; + border-radius: 8px; + background-color: #fff; + cursor: pointer; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23007bff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 15px center; + background-size: 15px; + transition: all 0.3s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +#filamentSelect:hover { + border-color: #007bff; + box-shadow: 0 3px 6px rgba(0, 123, 255, 0.1); +} + +#filamentSelect:focus { + border-color: #007bff; + outline: none; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25); +} + +#filamentSelect option { + padding: 8px 15px; + font-size: 16px; + background-color: #fff; + color: #000; /* Standard Textfarbe für alles außer dem Farbblock */ +} + +#filamentSelect option::first-letter { + font-size: 16px; + margin-right: 5px; +} + +#filamentSelect option::before { + content: ''; + display: inline-block; + width: 12px; + margin-right: 8px; +} + +/* Color Box im Select */ +.color-box { + display: inline-block; + width: 12px; + height: 12px; + border: 1px solid #333; + border-radius: 2px; + margin-right: 5px; + vertical-align: middle; +} + +#filamentSelect option span { + display: inline-block; + pointer-events: none; +} + +#filamentSelect option span:first-child { + margin-right: 5px; + font-size: 16px; +} + +/* Filament Select Option Styling */ +#filamentSelect option span.color-circle { + display: inline-block; + width: 12px; + height: 12px; + border-radius: 50%; + margin-right: 8px; + border: 1px solid #333; + vertical-align: middle; +} + +/* Custom Dropdown */ +.custom-dropdown { + position: relative; + width: 100%; + font-family: inherit; + cursor: default; /* Container selbst soll normalen Cursor haben */ +} + +.dropdown-button { + padding: 12px 15px; + border: 2px solid #e0e0e0; + border-radius: 8px; + background-color: #fff; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + transition: all 0.3s ease; +} + +.dropdown-button:hover { + border-color: #007bff; +} + +.selected-color { + width: 16px; + height: 16px; + border-radius: 50%; + border: 1px solid #333; + flex-shrink: 0; +} + +.dropdown-arrow { + margin-left: auto; + color: #007bff; + font-size: 12px; +} + +.dropdown-content { + display: none; + position: absolute; + top: 100%; + left: 0; + right: 0; + background-color: #fff; + border: 1px solid #e0e0e0; + border-radius: 8px; + margin-top: 4px; + max-height: 300px; + overflow-y: auto; + z-index: 1000; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.dropdown-content.show { + display: block; +} + +.dropdown-option { + padding: 10px 15px; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; +} + +.dropdown-option:hover { + background-color: #f8f9fa; +} + +.option-color { + width: 16px; + height: 16px; + border-radius: 50%; + border: 1px solid #333; + flex-shrink: 0; +} + +.notification { + position: fixed; + top: 20px; + right: 20px; + padding: 15px 25px; + border-radius: 4px; + color: white; + z-index: 1000; + animation: slideIn 0.3s ease-out; +} + +.notification.success { + background-color: #28a745; +} + +.notification.error { + background-color: #dc3545; +} + +.notification.fade-out { + opacity: 0; + transition: opacity 0.3s ease-out; +} + +@keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* Neue Styles für die Statistiken */ +.statistics-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin-top: 15px; +} + +.statistics-column { + background: #f8f9fa; + padding: 0; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); +} + +.statistics-column h3 { + color: #007bff; + margin-bottom: 5px; + padding-bottom: 8px; + border-bottom: 2px solid #e9ecef; + font-size: 1.1rem; +} + +.statistics-list { + list-style: none; + padding: 0; + margin: 0; +} + +.statistics-list li { + display: flex; + justify-content: space-between; + padding: 8px 5px 0 5px; + border-bottom: 1px solid #e9ecef; +} + +.statistics-list li:last-child { + border-bottom: none; +} + +.stat-label { + color: #495057; + font-weight: 500; +} + +.stat-value { + font-weight: bold; + color: #007bff; +} + +/* Responsive Design Anpassung */ +@media (max-width: 768px) { + .statistics-grid { + grid-template-columns: 1fr; + } +} + +.statistics-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + padding-bottom: 0; + border-bottom: 1px solid #e9ecef; +} + +.refresh-button { + display: flex; + align-items: center; + padding: 8px 16px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.2s; +} + +.refresh-button:hover { + background-color: #0056b3; +} + +.refresh-button:active { + background-color: #004494; +} + +.spools-info { + display: flex; + justify-content: flex-start; + gap: 20px; + margin-bottom: 15px; +} + +.spool-stat { + display: flex; + align-items: center; + gap: 8px; +} + +.spool-stat .stat-label { + color: #495057; + font-weight: 500; + white-space: nowrap; +} + +.spool-stat .stat-value { + font-weight: bold; + color: #007bff; +} + +/* Buttons und klickbare Elemente */ +button, +input[type="submit"], +.dropdown-button, +.dropdown-option, +.refresh-button, +.btn, +.styled-select, +select, +a { + cursor: pointer !important; +} + +/* Disabled Zustände */ +button:disabled, +input[type="submit"]:disabled, +.btn:disabled, +.styled-select:disabled { + cursor: not-allowed !important; + opacity: 0.7; +} + +/* Schreib-Button */ +#writeNfcButton { + background-color: #007bff; + color: white; + transition: background-color 0.3s, color 0.3s; + width: 160px; +} + +#writeNfcButton.writing { + background-color: #ffc107; + color: black; + width: 160px; +} + +#writeNfcButton.success { + background-color: #28a745; + color: white; + width: 160px; +} + +#writeNfcButton.error { + background-color: #dc3545; + color: white; + width: 160px; +} + +@keyframes dots { + 0% { content: ""; } + 33% { content: "."; } + 66% { content: ".."; } + 100% { content: "..."; } +} + +#writeNfcButton.writing::after { + content: "..."; + animation: dots 1s steps(3, end) infinite; +} + +.reboot-button { + background-color: #ff0000; + color: white; + padding: 10px 20px; + border: none; + border-radius: 4px; + margin-left: 10px; + cursor: pointer; +} + +.reboot-button:hover { + background-color: #cc0000; +} + +/* Bambu Settings Erweiterung */ +.bambu-settings { + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + max-width: 400px; + margin: 20px auto; +} + +.bambu-settings .input-group { + margin-bottom: 15px; + text-align: left; +} + +.bambu-settings .input-group label { + display: block; + margin-bottom: 5px; +} + +.bambu-settings .input-group input { + width: 100%; +} + +#bambuStatusMessage { + margin-top: 15px; + display: flex; + align-items: center; + gap: 10px; + justify-content: center; +} + + +.tray { + position: relative; +} + +.spool-button:hover { + opacity: 0.8; +} \ No newline at end of file diff --git a/html/waage.html b/html/waage.html new file mode 100644 index 0000000..04d81df --- /dev/null +++ b/html/waage.html @@ -0,0 +1,99 @@ +{{header}} +
+

Scale Configuration Page

+ +
+
+
Sacle Calibration
+ + +
+
+
+ + + +
+ + + + diff --git a/html/wifi.html b/html/wifi.html new file mode 100644 index 0000000..cb8ac85 --- /dev/null +++ b/html/wifi.html @@ -0,0 +1,12 @@ +{{header}} +
+

WiFi Configuration Page

+
+
+
+ +
+

Configure your WiFi settings here.

+
+ + diff --git a/img/doc-esp32-pinout-reference-wroom-devkit.png b/img/doc-esp32-pinout-reference-wroom-devkit.png new file mode 100644 index 0000000..d9ab42f Binary files /dev/null and b/img/doc-esp32-pinout-reference-wroom-devkit.png differ diff --git a/scripts/buildfs.sh b/scripts/buildfs.sh new file mode 100755 index 0000000..1266fb1 --- /dev/null +++ b/scripts/buildfs.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pio run --target buildfs \ No newline at end of file diff --git a/scripts/uploadfs.sh b/scripts/uploadfs.sh new file mode 100755 index 0000000..6ae9bf3 --- /dev/null +++ b/scripts/uploadfs.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pio run --target uploadfs \ No newline at end of file diff --git a/src/api.cpp b/src/api.cpp new file mode 100644 index 0000000..7049d9b --- /dev/null +++ b/src/api.cpp @@ -0,0 +1,471 @@ +#include "api.h" +#include +#include +#include "commonFS.h" + +bool spoolman_connected = false; +String spoolmanUrl = ""; + +struct SendToApiParams { + String httpType; + String spoolsUrl; + String updatePayload; +}; + +/* + // Spoolman Data + { + "version":"1.0", + "protocol":"openspool", + "color_hex":"AF7933", + "type":"ABS", + "min_temp":175, + "max_temp":275, + "brand":"Overture" + } + + // FilaMan Data + { + "version":"1.0", + "protocol":"openspool", + "color_hex":"AF7933", + "type":"ABS", + "min_temp":175, + "max_temp":275, + "brand":"Overture", + "sm_id": + } +*/ + +JsonDocument fetchSpoolsForWebsite() { + HTTPClient http; + String spoolsUrl = spoolmanUrl + apiUrl + "/spool"; + + Serial.print("Rufe Spool-Daten von: "); + Serial.println(spoolsUrl); + + http.begin(spoolsUrl); + int httpCode = http.GET(); + + JsonDocument filteredDoc; + if (httpCode == HTTP_CODE_OK) { + String payload = http.getString(); + JsonDocument doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print("Fehler beim Parsen der JSON-Antwort: "); + Serial.println(error.c_str()); + } else { + JsonArray spools = doc.as(); + JsonArray filteredSpools = filteredDoc.to(); + + for (JsonObject spool : spools) { + JsonObject filteredSpool = filteredSpools.createNestedObject(); + filteredSpool["extra"]["nfc_id"] = spool["extra"]["nfc_id"]; + + JsonObject filament = filteredSpool.createNestedObject("filament"); + filament["sm_id"] = spool["id"]; + filament["id"] = spool["filament"]["id"]; + filament["name"] = spool["filament"]["name"]; + filament["material"] = spool["filament"]["material"]; + filament["color_hex"] = spool["filament"]["color_hex"]; + filament["nozzle_temperature"] = spool["filament"]["extra"]["nozzle_temperature"]; // [190,230] + filament["price_meter"] = spool["filament"]["extra"]["price_meter"]; + filament["price_gramm"] = spool["filament"]["extra"]["price_gramm"]; + + JsonObject vendor = filament.createNestedObject("vendor"); + vendor["id"] = spool["filament"]["vendor"]["id"]; + vendor["name"] = spool["filament"]["vendor"]["name"]; + } + } + } else { + Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: "); + Serial.println(httpCode); + } + + http.end(); + return filteredDoc; +} + +JsonDocument fetchAllSpoolsInfo() { + HTTPClient http; + String spoolsUrl = spoolmanUrl + apiUrl + "/spool"; + + Serial.print("Rufe Spool-Daten von: "); + Serial.println(spoolsUrl); + + http.begin(spoolsUrl); + int httpCode = http.GET(); + + JsonDocument filteredDoc; + if (httpCode == HTTP_CODE_OK) { + String payload = http.getString(); + JsonDocument doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print("Fehler beim Parsen der JSON-Antwort: "); + Serial.println(error.c_str()); + } else { + JsonArray spools = doc.as(); + JsonArray filteredSpools = filteredDoc.to(); + + for (JsonObject spool : spools) { + JsonObject filteredSpool = filteredSpools.createNestedObject(); + filteredSpool["price"] = spool["price"]; + filteredSpool["remaining_weight"] = spool["remaining_weight"]; + filteredSpool["used_weight"] = spool["used_weight"]; + filteredSpool["extra"]["nfc_id"] = spool["extra"]["nfc_id"]; + + JsonObject filament = filteredSpool.createNestedObject("filament"); + filament["id"] = spool["filament"]["id"]; + filament["name"] = spool["filament"]["name"]; + filament["material"] = spool["filament"]["material"]; + filament["density"] = spool["filament"]["density"]; + filament["diameter"] = spool["filament"]["diameter"]; + filament["spool_weight"] = spool["filament"]["spool_weight"]; + filament["color_hex"] = spool["filament"]["color_hex"]; + + JsonObject vendor = filament.createNestedObject("vendor"); + vendor["id"] = spool["filament"]["vendor"]["id"]; + vendor["name"] = spool["filament"]["vendor"]["name"]; + + JsonObject extra = filament.createNestedObject("extra"); + extra["nozzle_temperature"] = spool["filament"]["extra"]["nozzle_temperature"]; + extra["price_gramm"] = spool["filament"]["extra"]["price_gramm"]; + extra["price_meter"] = spool["filament"]["extra"]["price_meter"]; + } + } + } else { + Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: "); + Serial.println(httpCode); + } + + http.end(); + return filteredDoc; +} + +void sendToApi(void *parameter) { + SendToApiParams* params = (SendToApiParams*)parameter; + + // Extrahiere die Werte + String httpType = params->httpType; + String spoolsUrl = params->spoolsUrl; + String updatePayload = params->updatePayload; + + + HTTPClient http; + http.begin(spoolsUrl); + http.addHeader("Content-Type", "application/json"); + + int httpCode = http.PUT(updatePayload); + if (httpType == "PATCH") httpCode = http.PATCH(updatePayload); + + if (httpCode == HTTP_CODE_OK) { + Serial.println("Gewicht der Spule erfolgreich aktualisiert"); + } else { + Serial.println("Fehler beim Aktualisieren des Gewichts der Spule"); + oledShowMessage("Spoolman update failed"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + } + + http.end(); + + // Speicher freigeben + delete params; + vTaskDelete(NULL); +} + +uint8_t updateSpoolTagId(String uidString, const char* payload) { + JsonDocument doc; + DeserializationError error = deserializeJson(doc, payload); + + if (error) { + Serial.print("Fehler beim JSON-Parsing: "); + Serial.println(error.c_str()); + return 0; + } + + // Überprüfe, ob die erforderlichen Felder vorhanden sind + if (!doc.containsKey("sm_id") || doc["sm_id"] == "") { + Serial.println("Keine Spoolman-ID gefunden."); + return 0; + } + + String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + doc["sm_id"].as(); + Serial.print("Update Spule mit URL: "); + Serial.println(spoolsUrl); + + // Update Payload erstellen + JsonDocument updateDoc; + updateDoc["extra"]["nfc_id"] = "\""+uidString+"\""; + + String updatePayload; + serializeJson(updateDoc, updatePayload); + Serial.print("Update Payload: "); + Serial.println(updatePayload); + + SendToApiParams* params = new SendToApiParams(); + if (params == nullptr) { + Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren."); + return 0; + } + params->httpType = "PATCH"; + params->spoolsUrl = spoolsUrl; + params->updatePayload = updatePayload; + + // Erstelle die Task + BaseType_t result = xTaskCreate( + sendToApi, // Task-Funktion + "SendToApiTask", // Task-Name + 4096, // Stackgröße in Bytes + (void*)params, // Parameter + 0, // Priorität + NULL // Task-Handle (nicht benötigt) + ); + + return 1; +} + +uint8_t updateSpoolWeight(String spoolId, uint16_t weight) { + String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + spoolId + "/measure"; + Serial.print("Update Spule mit URL: "); + Serial.println(spoolsUrl); + + // Update Payload erstellen + JsonDocument updateDoc; + updateDoc["weight"] = weight; + + String updatePayload; + serializeJson(updateDoc, updatePayload); + Serial.print("Update Payload: "); + Serial.println(updatePayload); + + SendToApiParams* params = new SendToApiParams(); + if (params == nullptr) { + Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren."); + return 0; + } + params->httpType = "PUT"; + params->spoolsUrl = spoolsUrl; + params->updatePayload = updatePayload; + + // Erstelle die Task + BaseType_t result = xTaskCreate( + sendToApi, // Task-Funktion + "SendToApiTask", // Task-Name + 4096, // Stackgröße in Bytes + (void*)params, // Parameter + 0, // Priorität + NULL // Task-Handle (nicht benötigt) + ); + + return 1; +} + +// #### Spoolman init +bool checkSpoolmanExtraFields() { + HTTPClient http; + String checkUrls[] = { + spoolmanUrl + apiUrl + "/field/spool", + spoolmanUrl + apiUrl + "/field/filament" + }; + + String spoolExtra[] = { + "nfc_id" + }; + + String filamentExtra[] = { + "nozzle_temperature", + "price_meter", + "price_gramm", + "bambu_setting_id", + "bambu_idx" + }; + + String spoolExtraFields[] = { + "{\"name\": \"NFC ID\"," + "\"key\": \"nfc_id\"," + "\"field_type\": \"text\"}" + }; + + String filamentExtraFields[] = { + "{\"name\": \"Nozzle Temp\"," + "\"unit\": \"°C\"," + "\"field_type\": \"integer_range\"," + "\"default_value\": \"[190,230]\"," + "\"key\": \"nozzle_temperature\"}", + + "{\"name\": \"Price/m\"," + "\"unit\": \"€\"," + "\"field_type\": \"float\"," + "\"key\": \"price_meter\"}", + + "{\"name\": \"Price/g\"," + "\"unit\": \"€\"," + "\"field_type\": \"float\"," + "\"key\": \"price_gramm\"}", + + "{\"name\": \"Bambu Setting ID\"," + "\"field_type\": \"text\"," + "\"key\": \"bambu_setting_id\"}", + + "{\"name\": \"Bambu IDX\"," + "\"field_type\": \"text\"," + "\"key\": \"bambu_idx\"}" + }; + + Serial.println("Überprüfe Extrafelder..."); + + int urlLength = sizeof(checkUrls) / sizeof(checkUrls[0]); + + for (uint8_t i = 0; i < urlLength; i++) { + Serial.println(); + Serial.println("-------- Prüfe Felder für "+checkUrls[i]+" --------"); + http.begin(checkUrls[i]); + int httpCode = http.GET(); + + if (httpCode == HTTP_CODE_OK) { + String payload = http.getString(); + JsonDocument doc; + DeserializationError error = deserializeJson(doc, payload); + if (!error) { + String* extraFields; + String* extraFieldData; + u16_t extraLength; + + if (i == 0) { + extraFields = spoolExtra; + extraFieldData = spoolExtraFields; + extraLength = sizeof(spoolExtra) / sizeof(spoolExtra[0]); + } else { + extraFields = filamentExtra; + extraFieldData = filamentExtraFields; + extraLength = sizeof(filamentExtra) / sizeof(filamentExtra[0]); + } + + for (uint8_t s = 0; s < extraLength; s++) { + bool found = false; + for (JsonObject field : doc.as()) { + if (field.containsKey("key") && field["key"] == extraFields[s]) { + Serial.println("Feld gefunden: " + extraFields[s]); + found = true; + break; + } + } + if (!found) { + Serial.println("Feld nicht gefunden: " + extraFields[s]); + + // Extrafeld hinzufügen + http.begin(checkUrls[i] + "/" + extraFields[s]); + http.addHeader("Content-Type", "application/json"); + int httpCode = http.POST(extraFieldData[s]); + + if (httpCode > 0) { + // Antwortscode und -nachricht abrufen + String response = http.getString(); + //Serial.println("HTTP-Code: " + String(httpCode)); + //Serial.println("Antwort: " + response); + if (httpCode != HTTP_CODE_OK) { + + return false; + } + } else { + // Fehler beim Senden der Anfrage + Serial.println("Fehler beim Senden der Anfrage: " + String(http.errorToString(httpCode))); + return false; + } + http.end(); + } + } + } + } + http.end(); + } + + Serial.println("-------- ENDE Prüfe Felder --------"); + Serial.println(); + + return true; +} + +bool checkSpoolmanInstance(const String& url) { + HTTPClient http; + String healthUrl = url + apiUrl + "/health"; + + Serial.print("Überprüfe Spoolman-Instanz unter: "); + Serial.println(healthUrl); + + http.begin(healthUrl); + int httpCode = http.GET(); + + if (httpCode > 0) { + if (httpCode == HTTP_CODE_OK) { + oledShowMessage("Spoolman available"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + + String payload = http.getString(); + JsonDocument doc; + DeserializationError error = deserializeJson(doc, payload); + if (!error && doc.containsKey("status")) { + const char* status = doc["status"]; + http.end(); + + if (!checkSpoolmanExtraFields()) { + Serial.println("Fehler beim Überprüfen der Extrafelder."); + + oledShowMessage("Spoolman Error creating Extrafields"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + + return false; + } + + spoolman_connected = true; + return strcmp(status, "healthy") == 0; + } + } + } + http.end(); + return false; +} + +bool saveSpoolmanUrl(const String& url) { + if (!checkSpoolmanInstance(url)) return false; + + JsonDocument doc; + doc["url"] = url; + Serial.print("Speichere URL in Datei: "); + Serial.println(url); + if (!saveJsonValue("/spoolman_url.json", doc)) { + Serial.println("Fehler beim Speichern der Spoolman-URL."); + } + spoolmanUrl = url; + + return true; +} + +String loadSpoolmanUrl() { + JsonDocument doc; + if (loadJsonValue("/spoolman_url.json", doc) && doc.containsKey("url")) { + return doc["url"].as(); + } + Serial.println("Keine gültige Spoolman-URL gefunden."); + return ""; +} + +bool initSpoolman() { + spoolmanUrl = loadSpoolmanUrl(); + spoolmanUrl.trim(); + if (spoolmanUrl == "") { + Serial.println("Keine Spoolman-URL gefunden."); + return false; + } + + bool success = checkSpoolmanInstance(spoolmanUrl); + if (!success) { + Serial.println("Spoolman nicht erreichbar."); + return false; + } + + oledShowTopRow(); + return true; +} \ No newline at end of file diff --git a/src/api.h b/src/api.h new file mode 100644 index 0000000..de9fab4 --- /dev/null +++ b/src/api.h @@ -0,0 +1,24 @@ +#ifndef API_H +#define API_H + +#include +#include // Include for AsyncWebServerRequest +#include "website.h" +#include "display.h" +#include + +extern bool spoolman_connected; +extern String spoolmanUrl; + +bool checkSpoolmanInstance(const String& url); +bool saveSpoolmanUrl(const String& url); +String loadSpoolmanUrl(); // Neue Funktion zum Laden der URL +bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafelder +JsonDocument fetchSpoolsForWebsite(); // API-Funktion für die Webseite +JsonDocument fetchAllSpoolsInfo(); +void sendAmsData(AsyncWebSocketClient *client); // Neue Funktion zum Senden von AMS-Daten +uint8_t updateSpoolTagId(String uidString, const char* payload); // Neue Funktion zum Aktualisieren eines Spools +uint8_t updateSpoolWeight(String spoolId, uint16_t weight); // Neue Funktion zum Aktualisieren des Gewichts +bool initSpoolman(); // Neue Funktion zum Initialisieren von Spoolman + +#endif diff --git a/src/bambu.cpp b/src/bambu.cpp new file mode 100644 index 0000000..06b4612 --- /dev/null +++ b/src/bambu.cpp @@ -0,0 +1,486 @@ +#include "bambu.h" +#include +#include +#include +#include +#include "bambu_cert.h" +#include "website.h" +#include "nfc.h" +#include "commonFS.h" +#include "esp_task_wdt.h" +#include "config.h" +#include "display.h" + +WiFiClient espClient; +SSLClient sslClient(&espClient); +PubSubClient client(sslClient); + +TaskHandle_t BambuMqttTask; + +String report_topic = ""; +//String request_topic = ""; +const char* bambu_username = "bblp"; +const char* bambu_ip = nullptr; +const char* bambu_accesscode = nullptr; +const char* bambu_serialnr = nullptr; +bool bambu_connected = false; + +// Globale Variablen für AMS-Daten +int ams_count = 0; +String amsJsonData; // Speichert das fertige JSON für WebSocket-Clients +AMSData ams_data[MAX_AMS]; // Definition des Arrays + +bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode) { + JsonDocument doc; + doc["bambu_ip"] = bambu_ip; + doc["bambu_accesscode"] = bambu_accesscode; + doc["bambu_serialnr"] = bambu_serialnr; + + if (!saveJsonValue("/bambu_credentials.json", doc)) { + Serial.println("Fehler beim Speichern der Bambu-Credentials."); + return false; + } + + vTaskDelay(100 / portTICK_PERIOD_MS); + if (!setupMqtt()) return false; + + return true; +} + +bool loadBambuCredentials() { + JsonDocument doc; + if (loadJsonValue("/bambu_credentials.json", doc) && doc.containsKey("bambu_ip")) { + // Temporäre Strings für die Werte + String ip = doc["bambu_ip"].as(); + String code = doc["bambu_accesscode"].as(); + String serial = doc["bambu_serialnr"].as(); + + ip.trim(); + code.trim(); + serial.trim(); + + // Dynamische Speicherallokation für die globalen Pointer + bambu_ip = strdup(ip.c_str()); + bambu_accesscode = strdup(code.c_str()); + bambu_serialnr = strdup(serial.c_str()); + + report_topic = "device/" + String(bambu_serialnr) + "/report"; + //request_topic = "device/" + String(bambu_serialnr) + "/request"; + return true; + } + Serial.println("Keine gültigen Bambu-Credentials gefunden."); + return false; +} + +String findFilamentIdx(String brand, String type) { + // JSON-Dokument für die Filament-Daten erstellen + JsonDocument doc; + + // Laden der bambu_filaments.json + if (!loadJsonValue("/bambu_filaments.json", doc)) { + Serial.println("Fehler beim Laden der Filament-Daten"); + return "GFL99"; // Fallback auf Generic PLA + } + + String searchKey; + + // 1. Suche nach Brand + Type Kombination + if (brand == "Bambu" || brand == "Bambulab") { + searchKey = "Bambu " + type; + } else if (brand == "PolyLite") { + searchKey = "PolyLite " + type; + } else if (brand == "eSUN") { + searchKey = "eSUN " + type; + } else if (brand == "Overture") { + searchKey = "Overture " + type; + } else if (brand == "PolyTerra") { + searchKey = "PolyTerra " + type; + } + + // Durchsuche alle Einträge nach der Brand + Type Kombination + for (JsonPair kv : doc.as()) { + if (kv.value().as() == searchKey) { + return kv.key().c_str(); + } + } + + // 2. Wenn nicht gefunden, suche nach Generic + Type + searchKey = "Generic " + type; + for (JsonPair kv : doc.as()) { + if (kv.value().as() == searchKey) { + return kv.key().c_str(); + } + } + + // 3. Wenn immer noch nichts gefunden, gebe GFL99 zurück (Generic PLA) + return "GFL99"; +} + +bool sendMqttMessage(String payload) { + Serial.println("Sending MQTT message"); + Serial.println(payload); + if (client.publish(report_topic.c_str(), payload.c_str())) + { + return true; + } + + return false; +} + +bool setBambuSpool(String payload) { + /* payload + //// set Spool + { + "print": { + "sequence_id": 0, + "command": "ams_filament_setting", + "ams_id": 0, // AMS ID 0-3 oder externe Spule 255 + "tray_id": 0, // Tray ID 0-3 oder externe Spule 254 + "tray_color": "000000FF", + "nozzle_temp_min": 170, + "nozzle_temp_max": 200, + "tray_type": "PETG", + "setting_id": "", + "tray_info_idx": "GFG99" + } + } + + + + //// Remove Spool + { + "print":{ + "ams_id":255, + "command":"ams_filament_setting", + "nozzle_temp_max": 0, + "nozzle_temp_min": 0, + "sequence_id": 0, + "setting_id": "", + "tray_color": "FFFFFFFF", + "tray_id": 254, + "tray_info_idx": "", + "tray_type": "", + } + } + */ + + Serial.println("Setting spool"); + + // Parse the JSON + JsonDocument doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print("Error parsing JSON: "); + Serial.println(error.c_str()); + return false; + } + + int amsId = doc["amsId"]; + int trayId = doc["trayId"]; + String color = doc["color"].as(); + color.toUpperCase(); + int minTemp = doc["nozzle_temp_min"]; + int maxTemp = doc["nozzle_temp_max"]; + String type = doc["type"].as(); + String brand = doc["brand"].as(); + String tray_info_idx = findFilamentIdx(brand, type); + + doc.clear(); + + doc["print"]["sequence_id"] = 0; + doc["print"]["command"] = "ams_filament_setting"; + doc["print"]["ams_id"] = amsId < 200 ? amsId-1 : 255; + doc["print"]["tray_id"] = trayId < 200 ? trayId-1 : 254; + doc["print"]["tray_color"] = color.length() == 8 ? color : color+"FF"; + doc["print"]["nozzle_temp_min"] = minTemp; + doc["print"]["nozzle_temp_max"] = maxTemp; + doc["print"]["tray_type"] = type; + doc["print"]["setting_id"] = ""; + doc["print"]["tray_info_idx"] = tray_info_idx; + + // Serialize the JSON + String output; + serializeJson(doc, output); + + if (sendMqttMessage(output)) { + Serial.println("Spool successfully set"); + } + else + { + Serial.println("Failed to set spool"); + return false; + } + + return true; +} + +// init +void mqtt_callback(char* topic, byte* payload, unsigned int length) { + String message; + for (int i = 0; i < length; i++) { + message += (char)payload[i]; + } + + // JSON-Dokument parsen + JsonDocument doc; + DeserializationError error = deserializeJson(doc, message); + if (error) { + Serial.print("Fehler beim Parsen des JSON: "); + Serial.println(error.c_str()); + return; + } + + // Prüfen, ob "print->upgrade_state" und "print.ams.ams" existieren + if (doc["print"].containsKey("upgrade_state")) { + // Prüfen ob AMS-Daten vorhanden sind + if (!doc["print"].containsKey("ams") || !doc["print"]["ams"].containsKey("ams")) { + return; + } + + JsonArray amsArray = doc["print"]["ams"]["ams"].as(); + + // Prüfe ob sich die AMS-Daten geändert haben + bool hasChanges = false; + + // Vergleiche jedes AMS und seine Trays + for (int i = 0; i < amsArray.size() && !hasChanges; i++) { + JsonObject amsObj = amsArray[i]; + int amsId = amsObj["id"].as(); + JsonArray trayArray = amsObj["tray"].as(); + + // Finde das entsprechende AMS in unseren Daten + int storedIndex = -1; + for (int k = 0; k < ams_count; k++) { + if (ams_data[k].ams_id == amsId) { + storedIndex = k; + break; + } + } + + if (storedIndex == -1) { + hasChanges = true; + break; + } + + // Vergleiche die Trays + for (int j = 0; j < trayArray.size() && j < 4 && !hasChanges; j++) { + JsonObject trayObj = trayArray[j]; + if (trayObj["tray_info_idx"].as() != ams_data[storedIndex].trays[j].tray_info_idx || + trayObj["tray_type"].as() != ams_data[storedIndex].trays[j].tray_type || + trayObj["tray_color"].as() != ams_data[storedIndex].trays[j].tray_color) { + hasChanges = true; + break; + } + } + } + + // Prüfe die externe Spule + if (!hasChanges && doc["print"].containsKey("vt_tray")) { + JsonObject vtTray = doc["print"]["vt_tray"]; + bool foundExternal = false; + + for (int i = 0; i < ams_count; i++) { + if (ams_data[i].ams_id == 255) { + foundExternal = true; + if (vtTray["tray_info_idx"].as() != ams_data[i].trays[0].tray_info_idx || + vtTray["tray_type"].as() != ams_data[i].trays[0].tray_type || + vtTray["tray_color"].as() != ams_data[i].trays[0].tray_color) { + hasChanges = true; + } + break; + } + } + if (!foundExternal) hasChanges = true; + } + + if (!hasChanges) return; + + // Fortfahren mit der bestehenden Verarbeitung, da Änderungen gefunden wurden + ams_count = amsArray.size(); + + // Restlicher bestehender Code... + for (int i = 0; i < ams_count && i < 16; i++) { + JsonObject amsObj = amsArray[i]; + JsonArray trayArray = amsObj["tray"].as(); + + ams_data[i].ams_id = i; // Setze die AMS-ID + for (int j = 0; j < trayArray.size() && j < 4; j++) { // Annahme: Maximal 4 Trays pro AMS + JsonObject trayObj = trayArray[j]; + + ams_data[i].trays[j].id = trayObj["id"].as(); + ams_data[i].trays[j].tray_info_idx = trayObj["tray_info_idx"].as(); + ams_data[i].trays[j].tray_type = trayObj["tray_type"].as(); + ams_data[i].trays[j].tray_sub_brands = trayObj["tray_sub_brands"].as(); + ams_data[i].trays[j].tray_color = trayObj["tray_color"].as(); + ams_data[i].trays[j].nozzle_temp_min = trayObj["nozzle_temp_min"].as(); + ams_data[i].trays[j].nozzle_temp_max = trayObj["nozzle_temp_max"].as(); + ams_data[i].trays[j].setting_id = trayObj["setting_id"].as(); + } + } + //Serial.println("----------------"); + //Serial.println(); + + // Sende die aktualisierten AMS-Daten an alle WebSocket-Clients + sendAmsData(nullptr); + + // Verarbeite erst die normalen AMS-Daten + for (int i = 0; i < amsArray.size() && i < 16; i++) { + JsonObject amsObj = amsArray[i]; + JsonArray trayArray = amsObj["tray"].as(); + + ams_data[i].ams_id = amsObj["id"].as(); + for (int j = 0; j < trayArray.size() && j < 4; j++) { + JsonObject trayObj = trayArray[j]; + ams_data[i].trays[j].id = trayObj["id"].as(); + ams_data[i].trays[j].tray_info_idx = trayObj["tray_info_idx"].as(); + // ... weitere Tray-Daten ... + } + } + + // Setze ams_count auf die Anzahl der normalen AMS + ams_count = amsArray.size(); + + // Wenn externe Spule vorhanden, füge sie hinzu + if (doc["print"].containsKey("vt_tray")) { + JsonObject vtTray = doc["print"]["vt_tray"]; + int extIdx = ams_count; // Index für externe Spule + ams_data[extIdx].ams_id = 255; // Spezielle ID für externe Spule + ams_data[extIdx].trays[0].id = 254; // Spezielle ID für externes Tray + ams_data[extIdx].trays[0].tray_info_idx = vtTray["tray_info_idx"].as(); + ams_data[extIdx].trays[0].tray_type = vtTray["tray_type"].as(); + ams_data[extIdx].trays[0].tray_sub_brands = vtTray["tray_sub_brands"].as(); + ams_data[extIdx].trays[0].tray_color = vtTray["tray_color"].as(); + ams_data[extIdx].trays[0].nozzle_temp_min = vtTray["nozzle_temp_min"].as(); + ams_data[extIdx].trays[0].nozzle_temp_max = vtTray["nozzle_temp_max"].as(); + ams_data[extIdx].trays[0].setting_id = vtTray["setting_id"].as(); + ams_count++; // Erhöhe ams_count für die externe Spule + } + + // Sende die aktualisierten AMS-Daten + sendAmsData(nullptr); + + // Erstelle JSON für WebSocket-Clients + JsonDocument wsDoc; + JsonArray wsArray = wsDoc.to(); + + for (int i = 0; i < ams_count; i++) { + JsonObject amsObj = wsArray.createNestedObject(); + amsObj["ams_id"] = ams_data[i].ams_id; + + JsonArray trays = amsObj.createNestedArray("tray"); + int maxTrays = (ams_data[i].ams_id == 255) ? 1 : 4; + + for (int j = 0; j < maxTrays; j++) { + JsonObject trayObj = trays.createNestedObject(); + trayObj["id"] = ams_data[i].trays[j].id; + trayObj["tray_info_idx"] = ams_data[i].trays[j].tray_info_idx; + trayObj["tray_type"] = ams_data[i].trays[j].tray_type; + trayObj["tray_sub_brands"] = ams_data[i].trays[j].tray_sub_brands; + trayObj["tray_color"] = ams_data[i].trays[j].tray_color; + trayObj["nozzle_temp_min"] = ams_data[i].trays[j].nozzle_temp_min; + trayObj["nozzle_temp_max"] = ams_data[i].trays[j].nozzle_temp_max; + trayObj["setting_id"] = ams_data[i].trays[j].setting_id; + } + } + + serializeJson(wsArray, amsJsonData); + sendAmsData(nullptr); + } +} + +void reconnect() { + // Loop until we're reconnected + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + + // Attempt to connect + if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) { + Serial.println("... re-connected"); + // ... and resubscribe + client.subscribe(report_topic.c_str()); + bambu_connected = true; + oledShowTopRow(); + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + bambu_connected = false; + oledShowTopRow(); + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +void mqtt_loop(void * parameter) { + oledShowMessage("Bambu Connected"); + bambu_connected = true; + oledShowTopRow(); + for(;;) { + if (pauseBambuMqttTask) { + vTaskDelay(10000); + } + + if (!client.connected()) { + reconnect(); + yield(); + esp_task_wdt_reset(); + vTaskDelay(100); + } + client.loop(); + } +} + +bool setupMqtt() { + // Wenn Bambu Daten vorhanden + bool success = loadBambuCredentials(); + vTaskDelay(100 / portTICK_PERIOD_MS); + + if (!success) { + Serial.println("Failed to load Bambu credentials"); + oledShowMessage("Bambu Credentials Missing"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + return false; + } + + if (success && bambu_ip != "" && bambu_accesscode != "" && bambu_serialnr != "") { + sslClient.setCACert(root_ca); + sslClient.setInsecure(); + client.setServer(bambu_ip, 8883); + + // Verbinden mit dem MQTT-Server + if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) { + client.setCallback(mqtt_callback); + client.setBufferSize(5120); + // Optional: Topic abonnieren + client.subscribe(report_topic.c_str()); + //client.subscribe(request_topic.c_str()); + Serial.println("MQTT-Client initialisiert"); + + oledShowTopRow(); + + xTaskCreatePinnedToCore( + mqtt_loop, /* Function to implement the task */ + "BambuMqtt", /* Name of the task */ + 10000, /* Stack size in words */ + NULL, /* Task input parameter */ + mqttTaskPrio, /* Priority of the task */ + &BambuMqttTask, /* Task handle. */ + mqttTaskCore); /* Core where the task should run */ + + } else { + Serial.println("Fehler: Konnte sich nicht beim MQTT-Server anmelden"); + oledShowMessage("Bambu Connection Failed"); + oledShowTopRow(); + vTaskDelay(2000 / portTICK_PERIOD_MS); + return false; + } + } else { + Serial.println("Fehler: Keine MQTT-Daten vorhanden"); + oledShowMessage("Bambu Credentials Missing"); + oledShowTopRow(); + vTaskDelay(2000 / portTICK_PERIOD_MS); + return false; + } + return true; +} \ No newline at end of file diff --git a/src/bambu.h b/src/bambu.h new file mode 100644 index 0000000..d3627c7 --- /dev/null +++ b/src/bambu.h @@ -0,0 +1,37 @@ +#ifndef BAMBU_H +#define BAMBU_H + +#include +#include + +struct TrayData { + uint8_t id; + String tray_info_idx; + String tray_type; + String tray_sub_brands; + String tray_color; + int nozzle_temp_min; + int nozzle_temp_max; + String setting_id; +}; + +#define MAX_AMS 17 // 16 normale AMS + 1 externe Spule +extern String amsJsonData; // Für die vorbereiteten JSON-Daten + +struct AMSData { + uint8_t ams_id; + TrayData trays[4]; // Annahme: Maximal 4 Trays pro AMS +}; + +extern bool bambu_connected; + +extern int ams_count; +extern AMSData ams_data[MAX_AMS]; + +bool loadBambuCredentials(); +bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode); +bool setupMqtt(); +void mqtt_loop(void * parameter); +bool setBambuSpool(String payload); + +#endif diff --git a/src/bambu_cert.h b/src/bambu_cert.h new file mode 100644 index 0000000..5622204 --- /dev/null +++ b/src/bambu_cert.h @@ -0,0 +1,45 @@ +const char root_ca[] PROGMEM = + "-----BEGIN CERTIFICATE-----\n" +"MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n" +"ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" +"b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n" +"MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" +"b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" +"ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" +"9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" +"IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" +"VOujw5H5SNz/0egwLX0tdHA234gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" +"93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" +"jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" +"AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n" +"A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n" +"U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n" +"N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n" +"o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n" +"5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n" +"rqXRfboQnoZsG4q5WTP468SQvvG5\n" +"-----END CERTIFICATE-----"; + +/* +-----BEGIN CERTIFICATE----- +MIIDZTCCAk2gAwIBAgIUV1FckwXElyek1onFnQ9kL7Bk4N8wDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCQ04xIjAgBgNVBAoMGUJCTCBUZWNobm9sb2dpZXMgQ28u +LCBMdGQxDzANBgNVBAMMBkJCTCBDQTAeFw0yMjA0MDQwMzQyMTFaFw0zMjA0MDEw +MzQyMTFaMEIxCzAJBgNVBAYTAkNOMSIwIAYDVQQKDBlCQkwgVGVjaG5vbG9naWVz +IENvLiwgTHRkMQ8wDQYDVQQDDAZCQkwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDL3pnDdxGOk5Z6vugiT4dpM0ju+3Xatxz09UY7mbj4tkIdby4H +oeEdiYSZjc5LJngJuCHwtEbBJt1BriRdSVrF6M9D2UaBDyamEo0dxwSaVxZiDVWC +eeCPdELpFZdEhSNTaT4O7zgvcnFsfHMa/0vMAkvE7i0qp3mjEzYLfz60axcDoJLk +p7n6xKXI+cJbA4IlToFjpSldPmC+ynOo7YAOsXt7AYKY6Glz0BwUVzSJxU+/+VFy +/QrmYGNwlrQtdREHeRi0SNK32x1+bOndfJP0sojuIrDjKsdCLye5CSZIvqnbowwW +1jRwZgTBR29Zp2nzCoxJYcU9TSQp/4KZuWNVAgMBAAGjUzBRMB0GA1UdDgQWBBSP +NEJo3GdOj8QinsV8SeWr3US+HjAfBgNVHSMEGDAWgBSPNEJo3GdOj8QinsV8SeWr +3US+HjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQABlBIT5ZeG +fgcK1LOh1CN9sTzxMCLbtTPFF1NGGA13mApu6j1h5YELbSKcUqfXzMnVeAb06Htu +3CoCoe+wj7LONTFO++vBm2/if6Jt/DUw1CAEcNyqeh6ES0NX8LJRVSe0qdTxPJuA +BdOoo96iX89rRPoxeed1cpq5hZwbeka3+CJGV76itWp35Up5rmmUqrlyQOr/Wax6 +itosIzG0MfhgUzU51A2P/hSnD3NDMXv+wUY/AvqgIL7u7fbDKnku1GzEKIkfH8hm +Rs6d8SCU89xyrwzQ0PR853irHas3WrHVqab3P+qNwR0YirL0Qk7Xt/q3O1griNg2 +Blbjg3obpHo9 +-----END CERTIFICATE----- +*/ \ No newline at end of file diff --git a/src/commonFS.cpp b/src/commonFS.cpp new file mode 100644 index 0000000..368f614 --- /dev/null +++ b/src/commonFS.cpp @@ -0,0 +1,56 @@ +#include "commonFS.h" + +bool saveJsonValue(const char* filename, const JsonDocument& doc) { + File file = SPIFFS.open(filename, "w"); + if (!file) { + Serial.print("Fehler beim Öffnen der Datei zum Schreiben: "); + Serial.println(filename); + return false; + } + return true; + if (serializeJson(doc, file) == 0) { + Serial.println("Fehler beim Serialisieren von JSON."); + file.close(); + return false; + } + file.close(); + return true; +} + +bool loadJsonValue(const char* filename, JsonDocument& doc) { + File file = SPIFFS.open(filename, "r"); + if (!file) { + Serial.print("Fehler beim Öffnen der Datei zum Lesen: "); + Serial.println(filename); + return false; + } + DeserializationError error = deserializeJson(doc, file); + file.close(); + if (error) { + Serial.print("Fehler beim Deserialisieren von JSON: "); + Serial.println(error.f_str()); + return false; + } + return true; +} + +bool initializeSPIFFS() { + // Erster Versuch + if (SPIFFS.begin(true)) { + Serial.println("SPIFFS mounted successfully."); + return true; + } + + // Formatierung versuchen + Serial.println("Failed to mount SPIFFS. Formatting..."); + SPIFFS.format(); + + // Zweiter Versuch nach Formatierung + if (SPIFFS.begin(true)) { + Serial.println("SPIFFS formatted and mounted successfully."); + return true; + } + + Serial.println("SPIFFS initialization failed completely."); + return false; +} \ No newline at end of file diff --git a/src/commonFS.h b/src/commonFS.h new file mode 100644 index 0000000..b94dadd --- /dev/null +++ b/src/commonFS.h @@ -0,0 +1,12 @@ +#ifndef COMMONFS_H +#define COMMONFS_H + +#include +#include +#include + +bool saveJsonValue(const char* filename, const JsonDocument& doc); +bool loadJsonValue(const char* filename, JsonDocument& doc); +bool initializeSPIFFS(); + +#endif diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..6b50d42 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,54 @@ +#include "config.h" + +// ################### Config Bereich Start +// ***** PN532 (RFID) +//#define PN532_SCK 18 +//#define PN532_MOSI 23 +//#define PN532_SS 5 +//#define PN532_MISO 19 +const uint8_t PN532_IRQ = 32; +const uint8_t PN532_RESET = 33; +// ***** PN532 + +// ***** HX711 (Waage) +// HX711 circuit wiring +const uint8_t LOADCELL_DOUT_PIN = 16; //16; +const uint8_t LOADCELL_SCK_PIN = 17; //17; +const uint8_t calVal_eepromAdress = 0; +const uint16_t SCALE_LEVEL_WEIGHT = 500; +uint16_t defaultScaleCalibrationValue = 430; +// ***** HX711 + +// ***** Display +// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) +// On an ESP32: 21(SDA), 22(SCL) +const int8_t OLED_RESET = -1; // Reset pin # (or -1 if sharing Arduino reset pin) +const uint8_t SCREEN_ADDRESS = 0x3C; ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 +const uint8_t SCREEN_WIDTH = 128; // OLED display width, in pixels +const uint8_t SCREEN_HEIGHT = 64; // OLED display height, in pixels +const uint8_t OLED_TOP_START = 0; +const uint8_t OLED_TOP_END = 16; +const uint8_t OLED_DATA_START = 17; +const uint8_t OLED_DATA_END = SCREEN_HEIGHT; +// ***** Display + +// ***** Webserver +const uint8_t webserverPort = 80; +// ***** Webserver + +// ***** API +const char* apiUrl = "/api/v1"; +// ***** API + +// ***** Task Prios +uint8_t rfidTaskCore = 1; +uint8_t rfidTaskPrio = 1; + +uint8_t rfidWriteTaskPrio = 1; + +uint8_t mqttTaskCore = 1; +uint8_t mqttTaskPrio = 1; + +uint8_t scaleTaskCore = 0; +uint8_t scaleTaskPrio = 1; +// ***** Task Prios diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..8cb867e --- /dev/null +++ b/src/config.h @@ -0,0 +1,48 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +extern const uint8_t PN532_IRQ; +extern const uint8_t PN532_RESET; + +extern const uint8_t LOADCELL_DOUT_PIN; +extern const uint8_t LOADCELL_SCK_PIN; +extern const uint8_t calVal_eepromAdress; +extern const uint16_t SCALE_LEVEL_WEIGHT; + +extern const int8_t OLED_RESET; +extern const uint8_t SCREEN_ADDRESS; +extern const uint8_t SCREEN_WIDTH; +extern const uint8_t SCREEN_HEIGHT; +extern const uint8_t OLED_TOP_START; +extern const uint8_t OLED_TOP_END; +extern const uint8_t OLED_DATA_START; +extern const uint8_t OLED_DATA_END; + +extern const char* apiUrl; +extern const uint8_t webserverPort; + +extern const unsigned char wifi_on[]; +extern const unsigned char wifi_off[]; +extern const unsigned char cloud_on[]; +extern const unsigned char cloud_off[]; + +extern const unsigned char icon_failed[]; +extern const unsigned char icon_success[]; +extern const unsigned char icon_transfer[]; +extern const unsigned char icon_loading[]; + +extern uint8_t rfidTaskCore; +extern uint8_t rfidTaskPrio; + +extern uint8_t rfidWriteTaskPrio; + +extern uint8_t mqttTaskCore; +extern uint8_t mqttTaskPrio; + +extern uint8_t scaleTaskCore; +extern uint8_t scaleTaskPrio; + +extern uint16_t defaultScaleCalibrationValue; +#endif \ No newline at end of file diff --git a/src/display.cpp b/src/display.cpp new file mode 100644 index 0000000..eda0777 --- /dev/null +++ b/src/display.cpp @@ -0,0 +1,225 @@ +#include "display.h" +#include "api.h" +#include +#include "icons.h" + +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + +bool wifiOn = false; + +void setupDisplay() { + if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { + Serial.println(F("SSD1306 allocation failed")); + for (;;); // Stoppe das Programm, wenn das Display nicht initialisiert werden kann + } + display.setTextColor(WHITE); + display.clearDisplay(); + display.display(); + + // Show initial display buffer contents on the screen -- + // the library initializes this with an Adafruit splash screen. + display.setTextColor(WHITE); + display.display(); + delay(1000); // Pause for 2 seconds + oledShowTopRow(); + delay(2000); +} + +void oledclearline() { + int x, y; + for (y = 0; y < 16; y++) { + for (x = 0; x < SCREEN_WIDTH; x++) { + display.drawPixel(x, y, BLACK); + } + } + display.display(); +} + +void oledcleardata() { + int x, y; + for (y = OLED_DATA_START; y < OLED_DATA_END; y++) { + for (x = 0; x < SCREEN_WIDTH; x++) { + display.drawPixel(x, y, BLACK); + } + } + display.display(); +} + +int oled_center_h(String text) { + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h); + return (SCREEN_WIDTH - w) / 2; +} + +int oled_center_v(String text) { + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(text, 0, OLED_DATA_START, &x1, &y1, &w, &h); + // Zentrierung nur im Datenbereich zwischen OLED_DATA_START und OLED_DATA_END + return OLED_DATA_START + ((OLED_DATA_END - OLED_DATA_START - h) / 2); +} + +std::vector splitTextIntoLines(String text, uint8_t textSize) { + std::vector lines; + display.setTextSize(textSize); + + // Text in Wörter aufteilen + std::vector words; + int pos = 0; + while (pos < text.length()) { + // Überspringe Leerzeichen am Anfang + while (pos < text.length() && text[pos] == ' ') pos++; + + // Finde nächstes Leerzeichen + int nextSpace = text.indexOf(' ', pos); + if (nextSpace == -1) { + // Letztes Wort + if (pos < text.length()) { + words.push_back(text.substring(pos)); + } + break; + } + // Wort hinzufügen + words.push_back(text.substring(pos, nextSpace)); + pos = nextSpace + 1; + } + + // Wörter zu Zeilen zusammenfügen + String currentLine = ""; + for (size_t i = 0; i < words.size(); i++) { + String testLine = currentLine; + if (currentLine.length() > 0) testLine += " "; + testLine += words[i]; + + // Prüfe ob diese Kombination auf die Zeile passt + int16_t x1, y1; + uint16_t lineWidth, h; + display.getTextBounds(testLine, 0, OLED_DATA_START, &x1, &y1, &lineWidth, &h); + + if (lineWidth <= SCREEN_WIDTH) { + // Passt noch in diese Zeile + currentLine = testLine; + } else { + // Neue Zeile beginnen + if (currentLine.length() > 0) { + lines.push_back(currentLine); + currentLine = words[i]; + } else { + // Ein einzelnes Wort ist zu lang + lines.push_back(words[i]); + } + } + } + + // Letzte Zeile hinzufügen + if (currentLine.length() > 0) { + lines.push_back(currentLine); + } + + Serial.println(lines.size()); + return lines; +} + +void oledShowMultilineMessage(String message, uint8_t size) { + std::vector lines; + int maxLines = 3; // Maximale Anzahl Zeilen für size 2 + + // Erste Prüfung mit aktueller Größe + lines = splitTextIntoLines(message, size); + + // Wenn mehr als maxLines Zeilen, reduziere Textgröße + if (lines.size() > maxLines && size > 1) { + size = 1; + lines = splitTextIntoLines(message, size); + } + + // Ausgabe + display.setTextSize(size); + int lineHeight = size * 8; + int totalHeight = lines.size() * lineHeight; + int startY = OLED_DATA_START + ((OLED_DATA_END - OLED_DATA_START - totalHeight) / 2); + + for (size_t i = 0; i < lines.size(); i++) { + display.setCursor(oled_center_h(lines[i]), startY + (i * lineHeight)); + display.print(lines[i]); + } + + display.display(); +} + +void oledShowMessage(String message, uint8_t size) { + oledcleardata(); + display.setTextSize(size); + display.setTextWrap(false); + + // Prüfe ob Text in eine Zeile passt + int16_t x1, y1; + uint16_t textWidth, h; + display.getTextBounds(message, 0, 0, &x1, &y1, &textWidth, &h); + + // Text passt in eine Zeile? + if (textWidth <= SCREEN_WIDTH) { + display.setCursor(oled_center_h(message), oled_center_v(message)); + display.print(message); + display.display(); + } else { + oledShowMultilineMessage(message, size); + } +} + +void oledShowTopRow() { + oledclearline(); + + if (bambu_connected == 1) { + display.drawBitmap(50, 0, bitmap_bambu_on , 16, 16, WHITE); + } else { + display.drawBitmap(50, 0, bitmap_off , 16, 16, WHITE); + } + + if (spoolman_connected == 1) { + display.drawBitmap(80, 0, bitmap_spoolman_on , 16, 16, WHITE); + } else { + display.drawBitmap(80, 0, bitmap_off , 16, 16, WHITE); + } + + if (wifiOn == 1) { + display.drawBitmap(107, 0, wifi_on , 16, 16, WHITE); + } else { + display.drawBitmap(107, 0, wifi_off , 16, 16, WHITE); + } + + display.display(); +} + +void oledShowIcon(const char* icon) { + oledcleardata(); + + uint16_t iconSize = OLED_DATA_END-OLED_DATA_START; + uint16_t iconStart = (SCREEN_WIDTH - iconSize) / 2; + + if (icon == "failed") { + display.drawBitmap(iconStart, OLED_DATA_START, icon_failed , iconSize, iconSize, WHITE); + } + else if (icon == "success") { + display.drawBitmap(iconStart, OLED_DATA_START, icon_success , iconSize, iconSize, WHITE); + } + else if (icon == "transfer") { + display.drawBitmap(iconStart, OLED_DATA_START, icon_transfer , iconSize, iconSize, WHITE); + } + else if (icon == "loading") { + display.drawBitmap(iconStart, OLED_DATA_START, icon_loading , iconSize, iconSize, WHITE); + } + + display.display(); +} + +void oledShowWeight(uint16_t weight) { + // Display Gewicht + oledcleardata(); + display.setTextSize(3); + display.setCursor(oled_center_h(String(weight)+" g"), OLED_DATA_START+10); + display.print(weight); + display.print(" g"); + display.display(); +} diff --git a/src/display.h b/src/display.h new file mode 100644 index 0000000..173c48e --- /dev/null +++ b/src/display.h @@ -0,0 +1,24 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include +#include +#include +#include "config.h" + + +extern Adafruit_SSD1306 display; +extern bool wifiOn; + +void setupDisplay(); +void oledclearline(); +void oledcleardata(); +int oled_center_h(String text); +int oled_center_v(String text); + +void oledShowWeight(uint16_t weight); +void oledShowMessage(String message, uint8_t size = 2); +void oledShowTopRow(); +void oledShowIcon(const char* icon); + +#endif diff --git a/src/icons.h b/src/icons.h new file mode 100644 index 0000000..74b3dda --- /dev/null +++ b/src/icons.h @@ -0,0 +1,126 @@ +#include + +/* +// Create Icons +https://javl.github.io/image2cpp/ +Size: 47x47 +BG Color: Black +Invert: True +Ohters in default +*/ + +const unsigned char wifi_on [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x1f, 0xf8, 0x78, 0x1e, 0x60, 0x06, 0x07, 0xe0, + 0x0f, 0xf0, 0x08, 0x10, 0x00, 0x00, 0x03, 0xc0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const unsigned char wifi_off [] PROGMEM = { + 0x00, 0x00, 0x20, 0x00, 0x30, 0x00, 0x1b, 0xf0, 0x3d, 0xfc, 0x7e, 0x1e, 0x63, 0x06, 0x07, 0xa0, + 0x1f, 0xd8, 0x08, 0x60, 0x01, 0xb0, 0x03, 0xd8, 0x01, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +// 'off', 16x16px +const unsigned char bitmap_off [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +// 'spoolman_on', 16x16px +const unsigned char bitmap_spoolman_on [] PROGMEM = { + 0x03, 0xc0, 0x08, 0xf0, 0x20, 0xfc, 0x00, 0xfc, 0x40, 0xfe, 0x70, 0xf0, 0xf8, 0xc1, 0xff, 0xc1, + 0xff, 0xc1, 0xf9, 0xc1, 0x70, 0xf0, 0x40, 0xfe, 0x00, 0xfc, 0x20, 0xfc, 0x08, 0xf0, 0x03, 0xc0 +}; + +// 'bambu_on', 16x16px +const unsigned char bitmap_bambu_on [] PROGMEM = { + 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x1c, 0x3e, 0x00, 0x3e, 0x40, + 0x30, 0x78, 0x00, 0x7c, 0x06, 0x7c, 0x1e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c +}; + +// 'failed', 47x47px +const unsigned char icon_failed [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x1f, 0xfc, 0x7f, 0xf0, 0x00, 0x00, 0x7f, + 0x80, 0x03, 0xf8, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0x00, + 0x03, 0xf0, 0x00, 0x00, 0x1f, 0x80, 0x07, 0xc0, 0x00, 0x00, 0x0f, 0x80, 0x07, 0x80, 0x00, 0x00, + 0x07, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x1f, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x1e, 0x03, + 0x80, 0x07, 0x80, 0xf0, 0x3e, 0x03, 0xc0, 0x07, 0x80, 0xf0, 0x3c, 0x03, 0xe0, 0x0f, 0x80, 0x78, + 0x3c, 0x01, 0xf0, 0x1f, 0x00, 0x78, 0x78, 0x00, 0xf8, 0x3e, 0x00, 0x38, 0x78, 0x00, 0x7c, 0x7c, + 0x00, 0x3c, 0x78, 0x00, 0x3c, 0xf8, 0x00, 0x3c, 0x78, 0x00, 0x3f, 0xf0, 0x00, 0x3c, 0x70, 0x00, + 0x1f, 0xf0, 0x00, 0x3c, 0x70, 0x00, 0x0f, 0xe0, 0x00, 0x3c, 0xf0, 0x00, 0x07, 0xc0, 0x00, 0x1c, + 0xf0, 0x00, 0x0f, 0xe0, 0x00, 0x3c, 0x70, 0x00, 0x1f, 0xf0, 0x00, 0x3c, 0x78, 0x00, 0x3f, 0xf0, + 0x00, 0x3c, 0x78, 0x00, 0x3e, 0xf8, 0x00, 0x3c, 0x78, 0x00, 0x7c, 0x7c, 0x00, 0x3c, 0x78, 0x00, + 0xf8, 0x3e, 0x00, 0x3c, 0x3c, 0x01, 0xf0, 0x1f, 0x00, 0x78, 0x3c, 0x03, 0xe0, 0x0f, 0x80, 0x78, + 0x3e, 0x03, 0xc0, 0x0f, 0x80, 0xf0, 0x1e, 0x03, 0x80, 0x07, 0x80, 0xf0, 0x1f, 0x00, 0x00, 0x00, + 0x01, 0xe0, 0x0f, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x07, 0x80, 0x00, 0x00, 0x07, 0xc0, 0x07, 0xc0, + 0x00, 0x00, 0x0f, 0xc0, 0x03, 0xf0, 0x00, 0x00, 0x1f, 0x80, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0x00, + 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf8, 0x3f, + 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, + 0x7f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +// 'loading', 47x47px +const unsigned char icon_loading [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, + 0x00, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xfe, + 0x00, 0x00, 0x00, 0x3f, 0xf9, 0xfc, 0x00, 0x00, 0x00, 0x7f, 0x83, 0xf8, 0x38, 0x00, 0x00, 0xff, + 0x07, 0xf0, 0x7c, 0x00, 0x00, 0xfc, 0x07, 0xe0, 0x7e, 0x00, 0x01, 0xf8, 0x0f, 0xc0, 0x3f, 0x00, + 0x03, 0xf0, 0x07, 0x80, 0x1f, 0x00, 0x03, 0xe0, 0x03, 0x00, 0x1f, 0x80, 0x07, 0xe0, 0x00, 0x00, + 0x0f, 0x80, 0x07, 0xc0, 0x00, 0x00, 0x0f, 0xc0, 0x07, 0xc0, 0x00, 0x00, 0x07, 0xc0, 0x0f, 0x80, + 0x00, 0x00, 0x07, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, + 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x0f, 0x80, 0x00, 0x00, + 0x03, 0xe0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x0f, 0x80, + 0x00, 0x00, 0x03, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x07, 0xc0, 0x07, 0xc0, 0x00, 0x00, 0x07, 0xc0, + 0x07, 0xc0, 0x00, 0x00, 0x07, 0xc0, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0x80, 0x03, 0xe0, 0x00, 0x00, + 0x1f, 0x80, 0x03, 0xf0, 0x00, 0x00, 0x1f, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0x00, 0x01, 0xfc, + 0x00, 0x00, 0x7e, 0x00, 0x00, 0xfe, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x7f, 0x80, 0x07, 0xf8, 0x00, + 0x00, 0x3f, 0xf0, 0x3f, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, + 0xc0, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +// 'success', 47x47px +const unsigned char icon_success [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, + 0x80, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xfc, 0x3f, 0xf0, 0x00, 0x00, 0x7f, + 0x80, 0x03, 0xfc, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0x00, + 0x03, 0xf0, 0x00, 0x00, 0x1f, 0x80, 0x03, 0xe0, 0x00, 0x00, 0x07, 0xc0, 0x07, 0xc0, 0x00, 0x00, + 0x03, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x01, 0xe0, 0x0f, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x1e, 0x00, + 0x00, 0x00, 0x00, 0xf0, 0x1e, 0x00, 0x00, 0x00, 0xc0, 0xf8, 0x3c, 0x00, 0x00, 0x01, 0xe0, 0x78, + 0x3c, 0x00, 0x00, 0x03, 0xe0, 0x78, 0x38, 0x00, 0x00, 0x03, 0xc0, 0x3c, 0x78, 0x00, 0x00, 0x07, + 0xc0, 0x3c, 0x78, 0x00, 0x00, 0x0f, 0x80, 0x3c, 0x78, 0x00, 0x00, 0x1f, 0x00, 0x1c, 0x78, 0x00, + 0x00, 0x1e, 0x00, 0x1c, 0x78, 0x0f, 0x00, 0x3e, 0x00, 0x1e, 0x78, 0x0f, 0x80, 0x7c, 0x00, 0x1e, + 0x78, 0x0f, 0xc0, 0xf8, 0x00, 0x1e, 0x78, 0x07, 0xe0, 0xf0, 0x00, 0x1c, 0x78, 0x03, 0xf1, 0xf0, + 0x00, 0x1c, 0x78, 0x01, 0xfb, 0xe0, 0x00, 0x3c, 0x78, 0x00, 0xff, 0xc0, 0x00, 0x3c, 0x38, 0x00, + 0x7f, 0x80, 0x00, 0x3c, 0x3c, 0x00, 0x3f, 0x80, 0x00, 0x78, 0x3c, 0x00, 0x1f, 0x00, 0x00, 0x78, + 0x1e, 0x00, 0x0e, 0x00, 0x00, 0xf8, 0x1e, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, + 0x01, 0xf0, 0x0f, 0x80, 0x00, 0x00, 0x01, 0xe0, 0x07, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x03, 0xe0, + 0x00, 0x00, 0x07, 0xc0, 0x03, 0xf0, 0x00, 0x00, 0x1f, 0x80, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0x00, + 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xfc, 0x3f, + 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, + 0x7f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +// 'transfer', 47x47px +const unsigned char icon_transfer [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x01, + 0xfe, 0x00, 0x00, 0x3f, 0xe0, 0x07, 0xff, 0x80, 0x00, 0xff, 0xc0, 0x0f, 0xff, 0xc0, 0x01, 0xfb, + 0xc0, 0x1f, 0x03, 0xe0, 0x03, 0xe3, 0x80, 0x3c, 0x00, 0xf0, 0x07, 0xc3, 0x80, 0x78, 0x00, 0x78, + 0x0f, 0x80, 0x00, 0x70, 0x00, 0x78, 0x0f, 0x00, 0x00, 0x70, 0x00, 0x38, 0x1e, 0x00, 0x00, 0xf0, + 0x00, 0x3c, 0x1c, 0x00, 0x00, 0xe0, 0x00, 0x3c, 0x3c, 0x00, 0x00, 0xe0, 0x00, 0x1c, 0x38, 0x00, + 0x00, 0xe0, 0x00, 0x3c, 0x38, 0x00, 0x00, 0xf0, 0x00, 0x3c, 0x38, 0x00, 0x00, 0x70, 0x00, 0x38, + 0x38, 0x00, 0x00, 0x78, 0x00, 0x78, 0x38, 0x00, 0x00, 0x78, 0x00, 0x78, 0x30, 0x00, 0x00, 0x3c, + 0x00, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x00, 0x00, + 0x00, 0x07, 0xff, 0x80, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xfc, 0x00, + 0x00, 0x00, 0x1f, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x1e, 0x00, 0x00, 0x18, 0x38, 0x00, + 0x0e, 0x00, 0x00, 0x1c, 0x38, 0x00, 0x0e, 0x00, 0x00, 0x1c, 0x38, 0x00, 0x0e, 0x00, 0x00, 0x3c, + 0x38, 0x00, 0x0e, 0x00, 0x00, 0x3c, 0x38, 0x00, 0x0e, 0x00, 0x00, 0x38, 0x38, 0x00, 0x0e, 0x00, + 0x00, 0x38, 0x38, 0x00, 0x0e, 0x00, 0x00, 0x78, 0x38, 0x00, 0x0e, 0x00, 0x00, 0x70, 0x38, 0x00, + 0x0e, 0x00, 0x00, 0xf0, 0x38, 0x00, 0x0e, 0x00, 0x01, 0xe0, 0x38, 0x00, 0x0e, 0x01, 0x83, 0xe0, + 0x38, 0x00, 0x0e, 0x03, 0x87, 0xc0, 0x3c, 0x00, 0x1e, 0x03, 0x9f, 0x80, 0x1f, 0x80, 0xfc, 0x07, + 0xff, 0x00, 0x1f, 0xff, 0xf8, 0x07, 0xfc, 0x00, 0x07, 0xff, 0xf0, 0x0f, 0xf0, 0x00, 0x01, 0xff, + 0xc0, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..e939ebc --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "website.h" +#include "api.h" +#include "display.h" +#include "bambu.h" +#include "nfc.h" +#include "scale.h" +#include "esp_task_wdt.h" +#include "commonFS.h" + +// ***** WIFI initialisieren +WiFiManager wm; +bool wm_nonblocking = false; +void initWiFi(); +// ################### Functions + +// ##### SETUP ##### +void setup() { + Serial.begin(115200); + + // Initialize SPIFFS + initializeSPIFFS(); + + // Start Display + setupDisplay(); + + // WiFiManager + initWiFi(); + + // Webserver + Serial.println("Starte Webserver"); + setupWebserver(server); + + // Spoolman API + // api.cpp + initSpoolman(); + + // Bambu MQTT + // bambu.cpp + setupMqtt(); + + // mDNS + Serial.println("Starte MDNS"); + if (!MDNS.begin("filaman")) { // Set the hostname to "esp32.local" + Serial.println("Error setting up MDNS responder!"); + while(1) { + delay(1000); + } + } + Serial.println("mDNS responder started"); + + startNfc(); + + start_scale(); + + // WDT initialisieren mit 10 Sekunden Timeout + bool panic = true; // Wenn true, löst ein WDT-Timeout einen System-Panik aus + esp_task_wdt_init(10, panic); + + // Aktuellen Task (loopTask) zum Watchdog hinzufügen + esp_task_wdt_add(NULL); + + // Optional: Andere Tasks zum Watchdog hinzufügen, falls nötig + // esp_task_wdt_add(task_handle); +} + + +unsigned long lastWeightReadTime = 0; +const unsigned long weightReadInterval = 1000; // 1 second +uint8_t weightSend = 0; +int16_t lastWeight = 0; +uint8_t wifiErrorCounter = 0; + +// ##### PROGRAM START ##### +void loop() { + // Überprüfe den WLAN-Status + if (WiFi.status() != WL_CONNECTED) { + wifiErrorCounter++; + wifiOn = false; + } else { + wifiErrorCounter = 0; + wifiOn = true; + } + if (wifiErrorCounter > 20) ESP.restart(); + + unsigned long currentMillis = millis(); + + // Falls WifiManager im nicht blockenden Modus ist + //if(wm_nonblocking) wm.process(); + + // Ausgabe der Waage auf Display + if (pauseMainTask == 0 && weight != lastWeight && hasReadRfidTag == 0) + { + (weight < 0) ? oledShowMessage("!! -1") : oledShowWeight(weight); + } + + // Wenn Timer abgelaufen und nicht gerade ein RFID-Tag geschrieben wird + if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag < 3) + { + lastWeightReadTime = currentMillis; + + // Prüfen ob die Waage korrekt genullt ist + if ((weight > 0 && weight < 5) || weight < 0) + { + scale_tare_counter++; + } + else + { + scale_tare_counter = 0; + } + + // Prüfen ob das Gewicht gleich bleibt und dann senden + if (weight == lastWeight && weight > 5) + { + weigthCouterToApi++; + } + else + { + weigthCouterToApi = 0; + weightSend = 0; + } + } + // reset weight counter after writing tag + if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag > 1) + { + weigthCouterToApi = 0; + } + + lastWeight = weight; + + // Wenn ein Tag mit SM id erkannte wurde und der Waage Counter anspricht an SM Senden + if (spoolId != "" && weigthCouterToApi > 5 && weightSend == 0 && hasReadRfidTag == 1) { + oledShowIcon("loading"); + if (updateSpoolWeight(spoolId, weight)) + { + oledShowIcon("success"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + weightSend = 1; + } + else + { + oledShowIcon("failed"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + } + } + + yield(); + esp_task_wdt_reset(); +} + +// ##### Funktionen für Konfiguration ##### +void initWiFi() { + WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP + + if(wm_nonblocking) wm.setConfigPortalBlocking(false); + wm.setConfigPortalTimeout(320); // Portal nach 5min schließen + + oledShowTopRow(); + oledShowMessage("WiFi Setup"); + + bool res; + // res = wm.autoConnect(); // auto generated AP name from chipid + res = wm.autoConnect("FilaMan"); // anonymous ap + // res = wm.autoConnect("spoolman","password"); // password protected ap + + if(!res) { + Serial.println("Failed to connect or hit timeout"); + // ESP.restart(); + oledShowTopRow(); + oledShowMessage("WiFi not connected Check Portal"); + } + else { + wifiOn = true; + + //if you get here you have connected to the WiFi + Serial.println("connected...yeey :)"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + oledShowTopRow(); + display.display(); + } +} +// ##### Funktionen für Konfiguration Ende ##### diff --git a/src/nfc.cpp b/src/nfc.cpp new file mode 100644 index 0000000..2cef262 --- /dev/null +++ b/src/nfc.cpp @@ -0,0 +1,503 @@ +#include "nfc.h" +#include +#include +#include +#include "config.h" +#include "website.h" +#include "api.h" +#include "esp_task_wdt.h" + +//Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS); +Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET); + +TaskHandle_t RfidReaderTask; + +JsonDocument rfidData; +String spoolId = ""; +String nfcJsonData = ""; +volatile bool pauseBambuMqttTask = false; + +volatile uint8_t hasReadRfidTag = 0; +// 0 = nicht gelesen +// 1 = erfolgreich gelesen +// 2 = fehler beim Lesen +// 3 = schreiben +// 4 = fehler beim Schreiben +// 5 = erfolgreich geschrieben +// 6 = reading +// ***** PN532 + + +// ##### Funktionen für RFID ##### +void payloadToJson(uint8_t *data) { + const char* startJson = strchr((char*)data, '{'); + const char* endJson = strrchr((char*)data, '}'); + + if (startJson && endJson && endJson > startJson) { + String jsonString = String(startJson, endJson - startJson + 1); + //Serial.print("Bereinigter JSON-String: "); + //Serial.println(jsonString); + + // JSON-Dokument verarbeiten + JsonDocument doc; // Passen Sie die Größe an den JSON-Inhalt an + DeserializationError error = deserializeJson(doc, jsonString); + + if (!error) { + const char* version = doc["version"]; + const char* protocol = doc["protocol"]; + const char* color_hex = doc["color_hex"]; + const char* type = doc["type"]; + int min_temp = doc["min_temp"]; + int max_temp = doc["max_temp"]; + const char* brand = doc["brand"]; + + Serial.println(); + Serial.println("-----------------"); + Serial.println("JSON-Parsed Data:"); + Serial.println(version); + Serial.println(protocol); + Serial.println(color_hex); + Serial.println(type); + Serial.println(min_temp); + Serial.println(max_temp); + Serial.println(brand); + Serial.println("-----------------"); + Serial.println(); + } else { + Serial.print("deserializeJson() failed: "); + Serial.println(error.f_str()); + } + } else { + Serial.println("Kein gültiger JSON-Inhalt gefunden oder fehlerhafte Formatierung."); + //writeJsonToTag("{\"version\":\"1.0\",\"protocol\":\"NFC\",\"color_hex\":\"#FFFFFF\",\"type\":\"Example\",\"min_temp\":10,\"max_temp\":30,\"brand\":\"BrandName\"}"); + } + } + +bool formatNdefTag() { + uint8_t ndefInit[] = { 0x03, 0x00, 0xFE }; // NDEF Initialisierungsnachricht + bool success = true; + int pageOffset = 4; // Startseite für NDEF-Daten auf NTAG2xx + + Serial.println(); + Serial.println("Formatiere NDEF-Tag..."); + + // Schreibe die Initialisierungsnachricht auf die ersten Seiten + for (int i = 0; i < sizeof(ndefInit); i += 4) { + if (!nfc.ntag2xx_WritePage(pageOffset + (i / 4), &ndefInit[i])) { + success = false; + break; + } + } + + return success; + } + +uint8_t ntag2xx_WriteNDEF(const char *payload) { + /* + if (!formatNdefTag()) { + Serial.println("Fehler beim Formatieren des NDEF-Tags."); + hasReadRfidTag = 2; + return 0; + } + */ + + uint8_t tagSize = 240; // 144 bytes is maximum for NTAG213 + Serial.print("Tag Size: ");Serial.println(tagSize); + + uint8_t pageBuffer[4] = {0, 0, 0, 0}; + Serial.println("Beginne mit dem Schreiben der NDEF-Nachricht..."); + + // Figure out how long the string is + uint8_t len = strlen(payload); + Serial.print("Länge der Payload: "); + Serial.println(len); + + Serial.print("Payload: ");Serial.println(payload); + + // Setup the record header + // See NFCForum-TS-Type-2-Tag_1.1.pdf for details + uint8_t pageHeader[21] = { + /* NDEF Message TLV - JSON Record */ + 0x03, /* Tag Field (0x03 = NDEF Message) */ + (uint8_t)(len+3+16), /* Payload Length (including NDEF header) */ + 0xD2, /* NDEF Record Header (TNF=0x2:MIME Media + SR + ME + MB) */ + 0x10, /* Type Length for the record type indicator */ + (uint8_t)(len), /* Payload len */ + 'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/', 'j', 's', 'o', 'n' + }; + + // Make sure the URI payload will fit in dataLen (include 0xFE trailer) + if ((len < 1) || (len + 1 > (tagSize - sizeof(pageHeader)))) + { + Serial.println(); + Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!"); + Serial.println("Fehler: Die Nutzlast passt nicht in die Datenlänge."); + Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!"); + Serial.println(); + return 0; + } + + //Serial.println(); + //Serial.print("Header Size: ");Serial.println(sizeof(pageHeader)); + + // Kombiniere Header und Payload + int totalSize = sizeof(pageHeader) + len; + uint8_t* combinedData = (uint8_t*) malloc(totalSize); + if (combinedData == NULL) + { + Serial.println("Fehler: Nicht genug Speicher vorhanden."); + return 0; + } + + // Überprüfe die Kombination von Header und Payload + /* + Serial.print("Header: "); + for (int i = 0; i < sizeof(pageHeader); i++) { + Serial.print(pageHeader[i], HEX); + Serial.print(" "); + } + Serial.println(); + + Serial.print("Payload: "); + for (int i = 0; i < len; i++) { + Serial.print(payload[i], HEX); + Serial.print(" "); + } + Serial.println(); + */ + + // Kombiniere Header und Payload + memcpy(combinedData, pageHeader, sizeof(pageHeader)); + memcpy(&combinedData[sizeof(pageHeader)], payload, len); + + // Überprüfe die Kombination von Header und Payload + /* + Serial.print("Kombinierte Daten: "); + for (int i = 0; i < totalSize; i++) { + Serial.print(combinedData[i], HEX); + Serial.print(" "); + } + Serial.println(); + */ + + // Schreibe die Seiten + uint8_t a = 0; + uint8_t i = 0; + while (totalSize > 0) { + memset(pageBuffer, 0, 4); + int bytesToWrite = (totalSize < 4) ? totalSize : 4; + memcpy(pageBuffer, combinedData + a, bytesToWrite); + + // Überprüfe die Schreibung der Seiten + /* + Serial.print("Seite "); + Serial.print(i); + Serial.print(": "); + for (int j = 0; j < bytesToWrite; j++) { + Serial.print(pageBuffer[j], HEX); + Serial.print(" "); + } + Serial.println(); + */ + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; + nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 500); + //Serial.print("Schreibe Seite: ");Serial.println(i); + + if (!(nfc.ntag2xx_WritePage(4+i, pageBuffer))) + { + Serial.println("Fehler beim Schreiben der Seite."); + free(combinedData); + return 0; + } + + //Serial.print("Seite geschrieben: ");Serial.println(i); + + yield(); + //esp_task_wdt_reset(); + + i++; + a += 4; + totalSize -= bytesToWrite; + } + + // Ensure the NDEF message is properly terminated + memset(pageBuffer, 0, 4); + pageBuffer[0] = 0xFE; // NDEF record footer + if (!(nfc.ntag2xx_WritePage(4+i, pageBuffer))) + { + Serial.println("Fehler beim Schreiben des End-Bits."); + free(combinedData); + return 0; + } + + Serial.println("NDEF-Nachricht erfolgreich geschrieben."); + free(combinedData); + return 1; +} + +bool decodeNdefAndReturnJson(const byte* encodedMessage) { + byte typeLength = encodedMessage[3]; + byte payloadLength = encodedMessage[4]; + + nfcJsonData = ""; + + for (int i = 2; i < payloadLength+2; i++) + { + nfcJsonData += (char)encodedMessage[3 + typeLength + i]; + } + + // JSON-Dokument verarbeiten + JsonDocument doc; // Passen Sie die Größe an den JSON-Inhalt an + DeserializationError error = deserializeJson(doc, nfcJsonData); + if (error) + { + nfcJsonData = ""; + Serial.println("Fehler beim Verarbeiten des JSON-Dokuments"); + Serial.print("deserializeJson() failed: "); + Serial.println(error.f_str()); + return false; + } + else + { + // Sende die aktualisierten AMS-Daten an alle WebSocket-Clients + Serial.println("JSON-Dokument erfolgreich verarbeitet"); + Serial.println(doc.as()); + if (doc["sm_id"] != "") + { + Serial.println("SPOOL-ID gefunden: " + doc["sm_id"].as()); + spoolId = doc["sm_id"].as(); + } + else + { + Serial.println("Keine SPOOL-ID gefunden."); + spoolId = ""; + oledShowMessage("Unknown Spool"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + } + } + + return true; +} + +void writeJsonToTag(void *parameter) { + const char* payload = (const char*)parameter; + + // Gib die erstellte NDEF-Message aus + Serial.println("Erstelle NDEF-Message..."); + hasReadRfidTag = 3; + vTaskSuspend(RfidReaderTask); + //pauseBambuMqttTask = true; + // aktualisieren der Website wenn sich der Status ändert + sendNfcData(nullptr); + + // Wait 10sec for tag + uint8_t success = 0; + String uidString = ""; + for (uint16_t i = 0; i < 20; i++) { + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 500); + if (success) { + for (uint8_t i = 0; i < uidLength; i++) { + uidString += String(uid[i], HEX); + if (i < uidLength - 1) { + uidString += ":"; // Optional: Trennzeichen hinzufügen + } + } + foundNfcTag(nullptr, success); + break; + } + + if (i == 0) oledShowMessage("Waiting for NFC-Tag"); + + yield(); + esp_task_wdt_reset(); + vTaskDelay(pdMS_TO_TICKS(1)); + } + + if (success) + { + oledShowIcon("transfer"); + // Schreibe die NDEF-Message auf den Tag + success = ntag2xx_WriteNDEF(payload); + if (success) + { + Serial.println("NDEF-Message erfolgreich auf den Tag geschrieben"); + //oledShowMessage("NFC-Tag written"); + oledShowIcon("success"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + hasReadRfidTag = 5; + // aktualisieren der Website wenn sich der Status ändert + sendNfcData(nullptr); + vTaskResume(RfidReaderTask); + pauseBambuMqttTask = false; + updateSpoolTagId(uidString, payload); + } + else + { + Serial.println("Fehler beim Schreiben der NDEF-Message auf den Tag"); + oledShowIcon("failed"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + hasReadRfidTag = 4; + } + } + else + { + Serial.println("Fehler: Kein Tag zu schreiben gefunden."); + oledShowMessage("No NFC-Tag found"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + hasReadRfidTag = 0; + } + + sendWriteResult(nullptr, success); + sendNfcData(nullptr); + + vTaskResume(RfidReaderTask); + pauseBambuMqttTask = false; + + vTaskDelete(NULL); +} + +void startWriteJsonToTag(const char* payload) { + char* payloadCopy = strdup(payload); + + // Erstelle die Task + xTaskCreate( + writeJsonToTag, // Task-Funktion + "WriteJsonToTagTask", // Task-Name + 4096, // Stackgröße in Bytes + (void*)payloadCopy, // Parameter + rfidWriteTaskPrio, // Priorität + NULL // Task-Handle (nicht benötigt) + ); +} + +void scanRfidTask(void * parameter) { + Serial.println("RFID Task gestartet"); + for(;;) { + // Wenn geschrieben wird Schleife aussetzen + if (hasReadRfidTag != 3) + { + uint8_t success; + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID + uint8_t uidLength; + + success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 1000); + + foundNfcTag(nullptr, success); + + if (success && hasReadRfidTag != 1) + { + // Display some basic information about the card + Serial.println("Found an ISO14443A card"); + + hasReadRfidTag = 6; + + oledShowIcon("transfer"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + + if (uidLength == 7) + { + uint8_t data[256]; + + // We probably have an NTAG2xx card (though it could be Ultralight as well) + Serial.println("Seems to be an NTAG2xx tag (7 byte UID)"); + + for (uint8_t i = 0; i < 45; i++) { + /* + if (i < uidLength) { + uidString += String(uid[i], HEX); + if (i < uidLength - 1) { + uidString += ":"; // Optional: Trennzeichen hinzufügen + } + } + */ + if (!nfc.mifareclassic_ReadDataBlock(i, data + (i - 4) * 4)) + { + break; // Stop if reading fails + } + // Check for NDEF message end + if (data[(i - 4) * 4] == 0xFE) + { + break; // End of NDEF message + } + + yield(); + esp_task_wdt_reset(); + vTaskDelay(pdMS_TO_TICKS(1)); + } + + if (!decodeNdefAndReturnJson(data)) + { + oledShowMessage("NFC-Tag unknown"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + hasReadRfidTag = 2; + } + else + { + hasReadRfidTag = 1; + } + + } + else + { + Serial.println("This doesn't seem to be an NTAG2xx tag (UUID length != 7 bytes)!"); + } + } + + if (!success && hasReadRfidTag > 0) + { + hasReadRfidTag = 0; + //uidString = ""; + nfcJsonData = ""; + Serial.println("Tag entfernt"); + oledShowWeight(0); + } + + // aktualisieren der Website wenn sich der Status ändert + sendNfcData(nullptr); + } + yield(); + } +} + +void startNfc() { + nfc.begin(); // Beginne Kommunikation mit RFID Leser + delay(1000); + unsigned long versiondata = nfc.getFirmwareVersion(); // Lese Versionsnummer der Firmware aus + if (! versiondata) { // Wenn keine Antwort kommt + Serial.println("Kann kein RFID Board finden !"); // Sende Text "Kann kein..." an seriellen Monitor + //delay(5000); + //ESP.restart(); + oledShowMessage("No RFID Board found"); + delay(2000); + } + else { + Serial.print("Chip PN5 gefunden"); Serial.println((versiondata >> 24) & 0xFF, HEX); // Sende Text und Versionsinfos an seriellen + Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); // Monitor, wenn Antwort vom Board kommt + Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); // + + nfc.SAMConfig(); + // Set the max number of retry attempts to read from a card + // This prevents us from waiting forever for a card, which is + // the default behaviour of the PN532. + //nfc.setPassiveActivationRetries(0x7F); + //nfc.setPassiveActivationRetries(0xFF); + + BaseType_t result = xTaskCreatePinnedToCore( + scanRfidTask, /* Function to implement the task */ + "RfidReader", /* Name of the task */ + 10000, /* Stack size in words */ + NULL, /* Task input parameter */ + rfidTaskPrio, /* Priority of the task */ + &RfidReaderTask, /* Task handle. */ + rfidTaskCore); /* Core where the task should run */ + + if (result != pdPASS) { + Serial.println("Fehler beim Erstellen des RFID Tasks"); + } else { + Serial.println("RFID Task erfolgreich erstellt"); + } + } +} \ No newline at end of file diff --git a/src/nfc.h b/src/nfc.h new file mode 100644 index 0000000..3610050 --- /dev/null +++ b/src/nfc.h @@ -0,0 +1,16 @@ +#ifndef NFC_H +#define NFC_H + +#include + +void startNfc(); +void scanRfidTask(void * parameter); +void startWriteJsonToTag(const char* payload); + +extern TaskHandle_t RfidReaderTask; +extern String nfcJsonData; +extern String spoolId; +extern volatile uint8_t hasReadRfidTag; +extern volatile bool pauseBambuMqttTask; + +#endif \ No newline at end of file diff --git a/src/scale.cpp b/src/scale.cpp new file mode 100644 index 0000000..b52ce28 --- /dev/null +++ b/src/scale.cpp @@ -0,0 +1,214 @@ +#include "nfc.h" +#include +#include +#include "config.h" +#include "HX711.h" +#include +#include "display.h" +#include "nfc.h" +#include "esp_task_wdt.h" + +HX711 scale; + +TaskHandle_t ScaleTask; + +int16_t weight = 0; + +uint8_t weigthCouterToApi = 0; +uint8_t scale_tare_counter = 0; +uint8_t pauseMainTask = 0; + +// ##### Funktionen für Waage ##### +uint8_t tareScale() { + Serial.println("Tare scale"); + scale.tare(); + + return 1; +} + +void scale_loop(void * parameter) { + Serial.println("++++++++++++++++++++++++++++++"); + Serial.println("Scale Loop started"); + Serial.println("++++++++++++++++++++++++++++++"); + for(;;) { + if (scale.is_ready()) + { + // Waage nochmal Taren, wenn zu lange Abweichung + if (scale_tare_counter >= 5) + { + scale.tare(); + scale_tare_counter = 0; + } + + weight = round(scale.get_units()); + } + + vTaskDelay(pdMS_TO_TICKS(100)); // Verzögerung, um die CPU nicht zu überlasten + } +} + +void start_scale() { + Serial.println("Prüfe Calibration Value"); + long calibrationValue; // calibration value (see example file "Calibration.ino") + //calibrationValue = 696.0; // uncomment this if you want to set the calibration value in the sketch + + EEPROM.begin(512); + EEPROM.get(calVal_eepromAdress, calibrationValue); // uncomment this if you want to fetch the calibration value from eeprom + + //calibrationValue = EEPROM.read(calVal_eepromAdress); + + Serial.print("Read Scale Calibration Value "); + Serial.println(calibrationValue); + + scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); + + if (isnan(calibrationValue) || calibrationValue < 1) calibrationValue = defaultScaleCalibrationValue; + + oledShowMessage("Scale Tare Please remove all"); + for (uint16_t i = 0; i < 2000; i++) { + yield(); + vTaskDelay(pdMS_TO_TICKS(1)); + esp_task_wdt_reset(); + } + + if (scale.wait_ready_timeout(1000)) + { + scale.set_scale(calibrationValue); // this value is obtained by calibrating the scale with known weights; see the README for details + scale.tare(); + } + + // Display Gewicht + oledShowWeight(0); + + Serial.println("starte Scale Task"); + BaseType_t result = xTaskCreatePinnedToCore( + scale_loop, /* Function to implement the task */ + "ScaleLoop", /* Name of the task */ + 10000, /* Stack size in words */ + NULL, /* Task input parameter */ + scaleTaskPrio, /* Priority of the task */ + &ScaleTask, /* Task handle. */ + scaleTaskCore); /* Core where the task should run */ + + if (result != pdPASS) { + Serial.println("Fehler beim Erstellen des ScaleLoop-Tasks"); + } else { + Serial.println("ScaleLoop-Task erfolgreich erstellt"); + } +} + +uint8_t calibrate_scale() { + long newCalibrationValue; + + //vTaskSuspend(RfidReaderTask); + vTaskDelete(RfidReaderTask); + pauseBambuMqttTask = true; + pauseMainTask = 1; + + if (scale.wait_ready_timeout(1000)) + { + scale.set_scale(); + oledShowMessage("Step 1 empty Scale"); + + for (uint16_t i = 0; i < 5000; i++) { + yield(); + vTaskDelay(pdMS_TO_TICKS(1)); + esp_task_wdt_reset(); + } + + scale.tare(); + Serial.println("Tare done..."); + Serial.print("Place a known weight on the scale..."); + + oledShowMessage("Step 2 Place the weight"); + + for (uint16_t i = 0; i < 5000; i++) { + yield(); + vTaskDelay(pdMS_TO_TICKS(1)); + esp_task_wdt_reset(); + } + + long newCalibrationValue = scale.get_units(10); + Serial.print("Result: "); + Serial.println(newCalibrationValue); + + newCalibrationValue = newCalibrationValue/SCALE_LEVEL_WEIGHT; + + if (newCalibrationValue > 0) + { + Serial.print("New calibration value has been set to: "); + Serial.println(newCalibrationValue); + Serial.print("Save this value to EEPROM adress "); + Serial.println(calVal_eepromAdress); + + //EEPROM.put(calVal_eepromAdress, newCalibrationValue); + EEPROM.put(calVal_eepromAdress, newCalibrationValue); + EEPROM.commit(); + + EEPROM.get(calVal_eepromAdress, newCalibrationValue); + //newCalibrationValue = EEPROM.read(calVal_eepromAdress); + + Serial.print("Read Value "); + Serial.println(newCalibrationValue); + + Serial.println("End calibration, revome weight"); + + oledShowMessage("Remove weight"); + + for (uint16_t i = 0; i < 2000; i++) { + yield(); + vTaskDelay(pdMS_TO_TICKS(1)); + esp_task_wdt_reset(); + } + + oledShowMessage("Calibration done"); + + for (uint16_t i = 0; i < 2000; i++) { + yield(); + vTaskDelay(pdMS_TO_TICKS(1)); + esp_task_wdt_reset(); + } + + //ESP.restart(); + } + else + { + { + Serial.println("Calibration value is invalid. Please recalibrate."); + + oledShowMessage("Calibration ERROR Try again"); + + for (uint16_t i = 0; i < 50000; i++) { + yield(); + vTaskDelay(pdMS_TO_TICKS(1)); + esp_task_wdt_reset(); + } + return 0; + } + } + } + else + { + Serial.println("HX711 not found."); + + oledShowMessage("HX711 not found"); + + for (uint16_t i = 0; i < 30000; i++) { + yield(); + vTaskDelay(pdMS_TO_TICKS(1)); + esp_task_wdt_reset(); + } + return 0; + } + + oledShowMessage("Scale Ready"); + + + Serial.println("starte Scale Task"); + start_scale(); + + pauseBambuMqttTask = false; + pauseMainTask = 0; + + return 1; +} diff --git a/src/scale.h b/src/scale.h new file mode 100644 index 0000000..d234da4 --- /dev/null +++ b/src/scale.h @@ -0,0 +1,18 @@ +#ifndef SCALE_H +#define SCALE_H + +#include +#include "HX711.h" + + +void start_scale(); +uint8_t calibrate_scale(); +uint8_t tareScale(); + +extern HX711 scale; +extern int16_t weight; +extern uint8_t weigthCouterToApi; +extern uint8_t scale_tare_counter; +extern uint8_t pauseMainTask; + +#endif \ No newline at end of file diff --git a/src/website.cpp b/src/website.cpp new file mode 100644 index 0000000..fedbb9a --- /dev/null +++ b/src/website.cpp @@ -0,0 +1,334 @@ +#include "website.h" +#include "commonFS.h" +#include "api.h" +#include +#include +//#include +#include "bambu.h" +#include "nfc.h" +#include "scale.h" +#include "esp_task_wdt.h" + +// Cache-Control Header definieren +#define CACHE_CONTROL "max-age=31536000" // Cache für 1 Jahr + +AsyncWebServer server(webserverPort); +AsyncWebSocket ws("/ws"); + +uint8_t lastSuccess = 0; +uint8_t lastHasReadRfidTag = 0; + +void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { + if (type == WS_EVT_CONNECT) { + Serial.println("Neuer Client verbunden!"); + // Sende die AMS-Daten an den neuen Client + sendAmsData(client); + sendNfcData(client); + foundNfcTag(client, hasReadRfidTag); + sendWriteResult(client, 0); + } else if (type == WS_EVT_DISCONNECT) { + Serial.println("Client getrennt."); + } else if (type == WS_EVT_DATA) { + String message = String((char*)data); + JsonDocument doc; + deserializeJson(doc, message); + + if (doc["type"] == "heartbeat") { + // Sende Heartbeat-Antwort + ws.text(client->id(), "{" + "\"type\":\"heartbeat\"," + "\"freeHeap\":" + String(ESP.getFreeHeap()/1024) + "," + "\"bambu_connected\":" + String(bambu_connected) + "," + "\"spoolman_connected\":" + String(spoolman_connected) + "" + "}"); + } + + else if (doc["type"] == "writeNfcTag") { + if (doc.containsKey("payload")) { + // Versuche NFC-Daten zu schreiben + String payloadString; + serializeJson(doc["payload"], payloadString); + startWriteJsonToTag(payloadString.c_str()); + } + } + + else if (doc["type"] == "scale") { + uint8_t success = 0; + if (doc["payload"] == "tare") { + success = tareScale(); + } + + if (doc["payload"] == "calibrate") { + success = calibrate_scale(); + } + + if (success) { + ws.textAll("{\"type\":\"scale\",\"payload\":\"success\"}"); + } else { + ws.textAll("{\"type\":\"scale\",\"payload\":\"error\"}"); + } + } + + else if (doc["type"] == "setBambuSpool") { + Serial.println(doc["payload"].as()); + setBambuSpool(doc["payload"]); + } + + else { + Serial.println("Unbekannter WebSocket-Typ: " + doc["type"].as()); + } + } +} + +// Funktion zum Laden und Ersetzen des Headers in einer HTML-Datei +String loadHtmlWithHeader(const char* filename) { + if (!SPIFFS.exists(filename) || !SPIFFS.exists("/header.html")) { + Serial.println("Fehler: Datei nicht gefunden!"); + return "Fehler: Datei nicht gefunden!"; + } + + // Lade den Header + File headerFile = SPIFFS.open("/header.html", "r"); + String header = headerFile.readString(); + headerFile.close(); + + // Lade die Hauptdatei + File file = SPIFFS.open(filename, "r"); + String html = file.readString(); + file.close(); + + // Ersetze den Platzhalter mit dem Header + html.replace("{{header}}", header); + + return html; +} + +void sendWriteResult(AsyncWebSocketClient *client, uint8_t success) { + // Sende Erfolg/Misserfolg an alle Clients + String response = "{\"type\":\"writeNfcTag\",\"success\":" + String(success ? "1" : "0") + "}"; + ws.textAll(response); +} + +void foundNfcTag(AsyncWebSocketClient *client, uint8_t success) { + if (success == lastSuccess) return; + ws.textAll("{\"type\":\"nfcTag\", \"payload\":{\"found\": " + String(success) + "}}"); + sendNfcData(nullptr); + lastSuccess = success; +} + +void sendNfcData(AsyncWebSocketClient *client) { + if (lastHasReadRfidTag == hasReadRfidTag) return; + if (hasReadRfidTag == 0) { + ws.textAll("{\"type\":\"nfcData\", \"payload\":{}}"); + } + else if (hasReadRfidTag == 1) { + ws.textAll("{\"type\":\"nfcData\", \"payload\":" + nfcJsonData + "}"); + } + else if (hasReadRfidTag == 2) + { + ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Empty Tag or Data not readable\"}}"); + } + else if (hasReadRfidTag == 3) + { + ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Schreibe Tag...\"}}"); + } + else if (hasReadRfidTag == 4) + { + ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Error writing to Tag\"}}"); + } + else if (hasReadRfidTag == 5) + { + ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Tag erfolgreich geschrieben\"}}"); + } + else + { + ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Something went wrong\"}}"); + } + lastHasReadRfidTag = hasReadRfidTag; +} + +void sendAmsData(AsyncWebSocketClient *client) { + if (ams_count > 0) { + ws.textAll("{\"type\":\"amsData\", \"payload\":" + amsJsonData + "}"); + } +} + +void setupWebserver(AsyncWebServer &server) { + // Lade die Spoolman-URL beim Booten + spoolmanUrl = loadSpoolmanUrl(); + Serial.print("Geladene Spoolman-URL: "); + Serial.println(spoolmanUrl); + + // Route für die Startseite + server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("Anfrage für / erhalten"); + String html = loadHtmlWithHeader("/index.html"); + request->send(200, "text/html", html); + }); + + // Route für Waage + server.on("/waage", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("Anfrage für /waage erhalten"); + String html = loadHtmlWithHeader("/waage.html"); + request->send(200, "text/html", html); + }); + + // Route für RFID + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("Anfrage für /rfid erhalten"); + String html = loadHtmlWithHeader("/rfid.html"); + request->send(200, "text/html", html); + Serial.println("RFID-Seite gesendet"); + }); + + /* + // Neue API-Route für das Abrufen der Spool-Daten + server.on("/api/spools", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("API-Aufruf: /api/spools"); + JsonDocument spoolsData = fetchSpoolsForWebsite(); + String response; + serializeJson(spoolsData, response); + request->send(200, "application/json", response); + }); + */ + + server.on("/api/url", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("API-Aufruf: /api/url"); + String jsonResponse = "{\"spoolman_url\": \"" + String(spoolmanUrl) + "\"}"; + request->send(200, "application/json", jsonResponse); + }); + + // Route für WiFi + server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("Anfrage für /wifi erhalten"); + String html = loadHtmlWithHeader("/wifi.html"); + request->send(200, "text/html", html); + }); + + // Route für Spoolman Setting + server.on("/spoolman", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("Anfrage für /spoolman erhalten"); + String html = loadHtmlWithHeader("/spoolman.html"); + html.replace("{{spoolmanUrl}}", spoolmanUrl); + + JsonDocument doc; + if (loadJsonValue("/bambu_credentials.json", doc) && doc.containsKey("bambu_ip")) { + html.replace("{{bambuIp}}", doc["bambu_ip"].as() ? doc["bambu_ip"].as() : ""); + html.replace("{{bambuSerial}}", doc["bambu_serialnr"].as() ? doc["bambu_serialnr"].as() : ""); + html.replace("{{bambuCode}}", doc["bambu_accesscode"].as() ? doc["bambu_accesscode"].as() : ""); + } + + request->send(200, "text/html", html); + }); + + // Route für das Überprüfen der Spoolman-Instanz + server.on("/api/checkSpoolman", HTTP_GET, [](AsyncWebServerRequest *request){ + if (!request->hasParam("url")) { + request->send(400, "application/json", "{\"success\": false, \"error\": \"Missing URL parameter\"}"); + return; + } + + String url = request->getParam("url")->value(); + url.trim(); + + bool healthy = saveSpoolmanUrl(url); + String jsonResponse = "{\"healthy\": " + String(healthy ? "true" : "false") + "}"; + + request->send(200, "application/json", jsonResponse); + }); + + // Route für das Überprüfen der Spoolman-Instanz + server.on("/api/bambu", HTTP_GET, [](AsyncWebServerRequest *request){ + if (!request->hasParam("bambu_ip") || !request->hasParam("bambu_serialnr") || !request->hasParam("bambu_accesscode")) { + request->send(400, "application/json", "{\"success\": false, \"error\": \"Missing parameter\"}"); + return; + } + + String bambu_ip = request->getParam("bambu_ip")->value(); + String bambu_serialnr = request->getParam("bambu_serialnr")->value(); + String bambu_accesscode = request->getParam("bambu_accesscode")->value(); + bambu_ip.trim(); + bambu_serialnr.trim(); + bambu_accesscode.trim(); + + bool success = saveBambuCredentials(bambu_ip, bambu_serialnr, bambu_accesscode); + + request->send(200, "application/json", "{\"healthy\": " + String(success ? "true" : "false") + "}"); + }); + + // Route für das Überprüfen der Spoolman-Instanz + server.on("/reboot", HTTP_GET, [](AsyncWebServerRequest *request){ + ESP.restart(); + }); + + // Route für das Laden der CSS-Datei + server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("Lade style.css"); + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/style.css.gz", "text/css"); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Cache-Control", CACHE_CONTROL); + request->send(response); + Serial.println("style.css gesendet"); + }); + + // Route für das Logo + server.on("/logo.png", HTTP_GET, [](AsyncWebServerRequest *request){ + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/logo.png.gz", "image/png"); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Cache-Control", CACHE_CONTROL); + request->send(response); + Serial.println("logo.png gesendet"); + }); + + // Route für Favicon + server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){ + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/favicon.ico", "image/x-icon"); + response->addHeader("Cache-Control", CACHE_CONTROL); + request->send(response); + Serial.println("favicon.ico gesendet"); + }); + + // Route für spool_in.png + server.on("/spool_in.png", HTTP_GET, [](AsyncWebServerRequest *request){ + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/spool_in.png.gz", "image/png"); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Cache-Control", CACHE_CONTROL); + request->send(response); + Serial.println("spool_in.png gesendet"); + }); + + // Route für JavaScript Dateien + server.on("/spoolman.js", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("Anfrage für /spoolman.js erhalten"); + AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/spoolman.js.gz", "text/javascript"); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Cache-Control", CACHE_CONTROL); + request->send(response); + Serial.println("Spoolman.js gesendet"); + }); + + server.on("/rfid.js", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("Anfrage für /rfid.js erhalten"); + AsyncWebServerResponse *response = request->beginResponse(SPIFFS,"/rfid.js.gz", "text/javascript"); + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Cache-Control", CACHE_CONTROL); + request->send(response); + Serial.println("RFID.js gesendet"); + }); + + // Fehlerbehandlung für nicht gefundene Seiten + server.onNotFound([](AsyncWebServerRequest *request){ + Serial.print("404 - Nicht gefunden: "); + Serial.println(request->url()); + request->send(404, "text/plain", "Seite nicht gefunden"); + }); + + // WebSocket-Route + ws.onEvent(onWsEvent); + server.addHandler(&ws); + ws.enable(true); + + // Starte den Webserver + server.begin(); + Serial.println("Webserver gestartet"); +} diff --git a/src/website.h b/src/website.h new file mode 100644 index 0000000..e35a25e --- /dev/null +++ b/src/website.h @@ -0,0 +1,26 @@ +#ifndef WEBSITE_H +#define WEBSITE_H + +#include +#include +#include "commonFS.h" +#include "api.h" +#include +#include +#include +#include "bambu.h" +#include "nfc.h" +#include "scale.h" +#include "esp_task_wdt.h" + +extern String spoolmanUrl; +extern AsyncWebServer server; +extern AsyncWebSocket ws; + +void setupWebserver(AsyncWebServer &server); +void sendAmsData(AsyncWebSocketClient *client); +void sendNfcData(AsyncWebSocketClient *client); +void foundNfcTag(AsyncWebSocketClient *client, uint8_t success); +void sendWriteResult(AsyncWebSocketClient *client, uint8_t success); + +#endif