Work on NMEA2000 device list

This commit is contained in:
2026-01-12 20:03:02 +01:00
parent c953340362
commit eb73c573b4
7 changed files with 485 additions and 22 deletions

32
README
View File

@@ -1,9 +1,9 @@
OBP Keypad
==========
OBP Keypad 6/1
==============
- Stromversorgung über M12-Anschluß über NMEA2000
- Eingangsbereich 6~21V
- Konfigurationsmodus durch langen Tastendruck auf DST
- Konfigurationsmodus durch langen Tastendruck (>3s) auf DST
- Tiefschlaf und Reset aus Konfigmodus heraus auswählbar
- Konfiguration über Web-Interface
- Buzzer für Tastendruck-Feedback
@@ -12,7 +12,7 @@ OBP Keypad
Optionen für später
- Helligkeitssensor z.B. zum automatischen LED dimmen
Pins umbelegen:
Pins umbelegen?:
I²C -> D0, D1 (GPIO 44, 43)
LEDs umsortieren: A0 bis A5 für die 6 LEDs
A6 als analoger Input für Sensor
@@ -72,11 +72,14 @@ Anschlußmöglichkeiten
Bemerkungen
-----------
Bei den aktuell verwendeten Tasten sind die Anschlußdrähte extrem
filigran. Leichtes Brechen und schlechte Verarbeitung.
Bei den aktuell verwendeten vorverkabelten Tasten sind die Anschlußdrähte
extrem filigran. Leichtes Brechen und schlechte Verarbeitung.
Besser Taster ohne Kabel verwenden. Schaltdraht mit 0,25mm² scheint am
besten geeignet zu sein, sowohl auf Tastenseite als auch zum Einführen
in den Terminalblock.
Es gibt verschiedene Varianten mit unterschiedlicher Federkraft.
Auswahl muß noch erfolgen
Finale Tasten-Auswahl muß noch erfolgen.
Beschaltung MCU Nano
@@ -106,8 +109,8 @@ ggf. ein E-Paper angeschlossen werden.
3 B D4 GPIO7
4 B D5 GPIO8
5 B D6 GPIO9
6 Y D7 GPIO10 Illumination
DST Y D8 GPIO17 Destination, On/Off
6 Y D7 GPIO10
DST Y D8 GPIO17 Destination, Konfiguration
LED Pin Remarks
------ ---------- ----------------------
@@ -124,7 +127,7 @@ ggf. ein E-Paper angeschlossen werden.
RX D10 GPIO21
BUZZER
TBD
GPIO43 temporär, piept allerdings beim flashen über USB und beim Reset
Bauteilliste (WIP)
------------
@@ -142,7 +145,9 @@ Bauteilliste (WIP)
1x Buzzer 12V, passiv
1x MOSFET 2N7000
1x Widerstand 150 Ohm
1x Kabelsatz für Tasten, 0,25 bis 0,5 mm²
1x Kabelsatz für Tasten, 0,25 mm², je 15cm lang
8x schwarz (GND)
7x farbig (Signal)
1x Terminalblock 2pol. 2,54mm schraubbar
1x 3D-Gehäuse bestehend auf Front- und Rückseite
1x Mutternwerkzeug 3D-Druck
@@ -155,6 +160,7 @@ Bauteilliste (WIP)
Stiftleiste 2,54mm
2x Jumper
1x Polyfuse
Schrumpfschlauch
Konfiguration
-------------
@@ -168,6 +174,10 @@ Konfiguration
NMEA2000
--------
Für verbesserten Zugriff auf die Geräteliste der NMEA2000-Bibliothek
wird eine erweiterte Funktion GetDeviceByIndex benötigt.
Aus diesem Grund wird mit einem Fork der Bibliothek gearbeitet.
Es werden keine eingehenden Pakete verarbeitet bis auf die ISO-Pflichtpakete
Es wird eine Geräteliste geführt

View File

@@ -13,6 +13,7 @@ lib_deps =
ESP32Async/AsyncTCP@3.4.9
ESP32Async/ESPAsyncWebServer@3.9.1
ttlappalainen/NMEA2000-library@4.24
# https://github.com/thooge/NMEA2000.git
robtillaart/SHT31@^0.5.2
# only these files will be emedded into firmware
@@ -20,6 +21,7 @@ board_build.embed_files =
lib/generated/index.html.gz
lib/generated/index.js.gz
lib/generated/sha256.js.gz
lib/generated/md5.js.gz
lib/generated/index.css.gz
lib/generated/config.json.gz

View File

@@ -4,6 +4,7 @@
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Update.h>
#include <Wire.h>
#include <SHT31.h> // temp. sensor
#include <NMEA2000.h>
@@ -11,6 +12,7 @@
#include <N2kMessages.h>
#include "main.h"
#include "Nmea2kTwai.h"
#include "N2kDeviceList.h"
#include <map>
#include "mbedtls/md.h" // for SHA256
@@ -112,6 +114,7 @@ uint8_t rgb_brightness = 64;
uint buzzerpower = 50; // TBD make use of this
uint8_t keycode[6]; // configurable keycodes
uint8_t longcode[6]; // configurable keycodes for long pressed keys
SHT31 sht(SHT31_ADDRESS);
bool sht_available = false;
@@ -120,7 +123,7 @@ float hum = 0.0;
int nodeid; // NMEA2000 id on bus
Nmea2kTwai &NMEA2000=*(new Nmea2kTwai(CAN_TX, CAN_RX, CAN_RECOVERY_PERIOD));
tN2kDeviceList *pN2kDeviceList;
String processor(const String& var) {
// dummy for now
@@ -300,6 +303,12 @@ void setup() {
keycode[3] = preferences.getInt("key4", 4);
keycode[4] = preferences.getInt("key5", 5);
keycode[5] = preferences.getInt("key6", 6);
longcode[0] = preferences.getInt("key1long", 11);
longcode[1] = preferences.getInt("key2long", 12);
longcode[2] = preferences.getInt("key3long", 13);
longcode[3] = preferences.getInt("key4long", 14);
longcode[4] = preferences.getInt("key5long", 15);
longcode[5] = preferences.getInt("key6long", 16);
preferences.end();
// Configure I/O pins
@@ -404,6 +413,31 @@ void setup() {
serializeJson(doc, out);
request->send(200, "application/json", out);
});
server.on("/api/devicelist", HTTP_GET, [](AsyncWebServerRequest *request){
// NMEA2000 device list
AsyncResponseStream *response = request->beginResponseStream("application/json");
response->print("[");
bool first = true;
for (int i = 0; i <= 252; i++) {
const tNMEA2000::tDevice *d = pN2kDeviceList->FindDeviceBySource(i);
if (d == nullptr) {
continue;
}
uint64_t NAME = d->GetName();
char hex_name[17];
snprintf(hex_name, sizeof(hex_name), "%08X%08X", (uint32_t)(NAME >> 32), (uint32_t)(NAME & 0xFFFFFFFF));
if (!first) {
response->print(",");
} else {
first = false;
}
// TODO last seen?
response->printf("{\"source\":%d,\"name\":\"%s\",\"manuf\":\"%d\",\"model\":\"%s\"}",
i, hex_name, d->GetManufacturerCode(), d->GetModelID());
}
response->print("]");
request->send(response);
});
server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request){
StaticJsonDocument<512> doc;
doc["systemName"] = "Keypad1";
@@ -428,6 +462,13 @@ void setup() {
doc["key4"] = keycode[BUTTON_4];
doc["key5"] = keycode[BUTTON_5];
doc["key6"] = keycode[BUTTON_6];
doc["key1long"] = longcode[BUTTON_1];
doc["key2long"] = longcode[BUTTON_2];
doc["key3long"] = longcode[BUTTON_3];
doc["key4long"] = longcode[BUTTON_4];
doc["key5long"] = longcode[BUTTON_5];
doc["key6long"] = longcode[BUTTON_6];
doc["envInterval"] = 5;
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
@@ -466,11 +507,33 @@ void setup() {
request->send(200, "application/json", out);
});
server.on("/api/update", HTTP_POST, [](AsyncWebServerRequest *request){
// the request handler is triggered after the upload has finished...
// create the response, add header, and send response
StaticJsonDocument<100> doc;
doc["status"] = "FAILED";
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
}, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
// this is the new image upload part
Serial.print("Retrieving firmware image named: ");
Serial.println(filename);
if (index == 0) {
if (! Update.begin(UPDATE_SIZE_UNKNOWN)) {
Update.printError(Serial);
}
}
if (Update.write(data, len) != len) {
Update.printError(Serial);
}
if (final) {
if (!Update.end(true)) {
Update.printError(Serial);
}
}
});
// TODO POST vom Client entgegennehmen
@@ -501,21 +564,31 @@ void setup() {
4 // Industrygoup=Marine
);
NMEA2000.SetForwardType(tNMEA2000::fwdt_Text);
// Debug
// Debug Start
// NMEA2000.SetForwardStream(&Serial);
// NMEA2000.SetForwardType(tNMEA2000::fwdt_Text);
// NMEA2000.SetForwardOwnMessages(true);
// NMEA2000.SetDebugMode(tNMEA2000::dm_2);
// Debug End
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, nodeid);
//TODO: N2km_NodeOnly N2km_ListenAndNode?
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, nodeid);
NMEA2000.SetForwardOwnMessages(false);
NMEA2000.SetHeartbeatIntervalAndOffset(NMEA2000_HEARTBEAT_INTERVAL);
const unsigned long TransmitPGNs[] = {
127502UL,
// Features?
// 130311 environment
// 130316 temperature extended range
const unsigned long TransmitPGNs[] PROGMEM= {
127502UL, // switch bank control
130312UL, // temperature
130313UL, // humidity
0
};
NMEA2000.ExtendTransmitMessages(TransmitPGNs);
pN2kDeviceList = new tN2kDeviceList(&NMEA2000);
// Debug: NMEA2000.EnableForward(true);
NMEA2000.Open();
// internal user led (red)
@@ -618,6 +691,41 @@ void atariKeyclick() {
ledcWriteTone(LEDC_CHANNEL, 0);
}
void print_n2k_devicelist() {
if (!pN2kDeviceList) {
Serial.println("Devicelist not initialized");
}
if (!pN2kDeviceList->ReadResetIsListUpdated()) {
// no changes, nothing to do
Serial.println("Devicelist empty or unchanged");
return;
}
Serial.println("---- NMEA2000 Device List ----");
for (int i = 0; i <= 252; i++) {
const tNMEA2000::tDevice *d = pN2kDeviceList->FindDeviceBySource(i);
if (d == nullptr) {
continue;
}
// age in milliseconds
unsigned long lastseen = pN2kDeviceList->GetDeviceLastMessageTime(i);
Serial.printf("Device %d age %d ms\n", i, lastseen);
uint64_t NAME = d->GetName();
char hex_name[17];
snprintf(hex_name, sizeof(hex_name), "%08X%08X",
(uint32_t)(NAME >> 32), (uint32_t)(NAME & 0xFFFFFFFF));
Serial.printf("Src:%d Man:%d Model:%s (0x%s)\n",
d->GetSource(),
d->GetManufacturerCode(),
d->GetModelID(),
hex_name
);
}
Serial.println("------------------------------");
}
// rename to: void sendSwitchBank()
void SendToN2K(uint8_t keycode) {
tN2kMsg N2kMsg;
tN2kBinaryStatus bankstatus;
@@ -636,6 +744,20 @@ void SendToN2K(uint8_t keycode) {
Serial.println(" Action=On");
}
void send_sensor_temphum(float temp_k, float hum_perc) {
tN2kMsg N2kMsg;
static unsigned char SID = 0;
unsigned char instance = 0;
tN2kTempSource temp_src = N2kts_OutsideTemperature; // 1=outside, 2=inside
tN2kHumiditySource hum_src = N2khs_OutsideHumidity; // 0=inside, 1=outside
Serial.printf("Sending temp=%f K, hum=%f %%\n", temp_k, hum_perc);
SetN2kPGN130312(N2kMsg, SID, instance, temp_src, temp_k);
NMEA2000.SendMsg(N2kMsg);
SetN2kPGN130313(N2kMsg, SID, instance, hum_src, hum_perc);
NMEA2000.SendMsg(N2kMsg);
SID = (SID + 1) % 256;
}
void loop() {
ButtonEvent event;
@@ -684,17 +806,29 @@ void loop() {
if (mode == 'N') {
mode = 'C';
analogWrite(RGBLED_B, rgb_brightness); // blue status indicator
Serial.println("Entering config mode");
} else {
mode = 'N';
analogWrite(RGBLED_B, 0);
Serial.println("Leaving config mode");
}
}
} else {
// normal button
if (mode == 'N') {
// TODO send key code to destination
// Send key code to destination
atariKeyclick();
SendToN2K(keycode[event.buttonId]);
if ((event.pressType == ButtonPressType::SHORT)) {
SendToN2K(keycode[event.buttonId]);
} else if ((event.pressType == ButtonPressType::MEDIUM)) {
SendToN2K(longcode[event.buttonId]);
}
// Debug:
if ((event.buttonId == BUTTON_1)
and (event.pressType == ButtonPressType::MEDIUM))
{
print_n2k_devicelist();
}
} else {
// online config mode
switch (event.buttonId) {
@@ -777,8 +911,11 @@ void loop() {
if (millis() - lastSensor >= 5000) {
lastSensor = millis();
sht.read();
temp = sht.getTemperature();
hum = sht.getHumidity();
temp = sht.getTemperature(); // °C
hum = sht.getHumidity(); // Percent
// Send environment data to NMEA2000
send_sensor_temphum(temp + 273.15, hum);
}
// development heartbeat

View File

@@ -136,6 +136,16 @@
"description": "The keycode to send for key 1 (leftmost) [1 .. 28]",
"category": "Keys"
},
{
"name": "key1long",
"label": "Key 1 long code",
"type": "number",
"default": 12,
"min": 1,
"max": 28,
"description": "The keycode to send for key 1 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{
"name": "key2",
"label": "Key 2 code",
@@ -146,6 +156,16 @@
"description": "The keycode to send for key 2 (second from left) [1 .. 28]",
"category": "Keys"
},
{
"name": "key2long",
"label": "Key 2 long code",
"type": "number",
"default": 12,
"min": 1,
"max": 28,
"description": "The keycode to send for key 2 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{
"name": "key3",
"label": "Key 3 code",
@@ -156,6 +176,16 @@
"description": "The keycode to send for key 3 (third from left) [1 .. 28]",
"category": "Keys"
},
{
"name": "key3long",
"label": "Key 3 long code",
"type": "number",
"default": 13,
"min": 1,
"max": 28,
"description": "The keycode to send for key 3 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{
"name": "key4",
"label": "Key 4 code",
@@ -166,6 +196,16 @@
"description": "The keycode to send for key 4 (fourth from left) [1 .. 28]",
"category": "Keys"
},
{
"name": "key4long",
"label": "Key 4 long code",
"type": "number",
"default": 14,
"min": 1,
"max": 28,
"description": "The keycode to send for key 4 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{
"name": "key5",
"label": "Key 5 code",
@@ -176,6 +216,16 @@
"description": "The keycode to send for key 5 (fifth from left) [1 .. 28]",
"category": "Keys"
},
{
"name": "key5long",
"label": "Key 5 long code",
"type": "number",
"default": 15,
"min": 1,
"max": 28,
"description": "The keycode to send for key 5 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{
"name": "key6",
"label": "Key 6 code",
@@ -185,5 +235,47 @@
"max": 28,
"description": "The keycode to send for key 6 (rightmost) [1 .. 28]",
"category": "Keys"
},
{
"name": "key6long",
"label": "Key 6 long code",
"type": "number",
"default": 16,
"min": 1,
"max": 28,
"description": "The keycode to send for key 6 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{
"name": "n2kDestA",
"label": "Destination A",
"type": "list",
"description": "Destination device A",
"category": "NMEA2000"
},
{
"name": "n2kDestB",
"label": "Destination B",
"type": "list",
"description": "Destination device B",
"category": "NMEA2000"
},
{
"name": "n2kDestC",
"label": "Destination C",
"type": "list",
"description": "Destination device C",
"category": "NMEA2000"
},
{
"name": "envInterval",
"label":"Environment Data Interval",
"type": "number",
"default": "5",
"check": "checkMinMax",
"min": 1,
"max": 300,
"description": "interval seconds to send environment data [1..300]",
"category": "NMEA2000"
}
]

View File

@@ -12,11 +12,12 @@ if (!window.isSecureContext) {
}
</script>
<script type="text/javascript" src="index.js"></script>
<script type="text/javascript" src="md5.js"></script>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div class="main">
<h1 id="headline">OBPkb</h1>
<h1 id="headline">OBPkb61</h1>
<div class="row">
<span class="label" id="conn_label">disconnected</span>
<span class="value" id="connected"></span>
@@ -24,6 +25,7 @@ if (!window.isSecureContext) {
<div id="tabs">
<div class="tab active" data-page="statusPage">Status</div>
<div class="tab" data-page="configPage">Config</div>
<div class="tab" data-page="devicePage">Devices</div>
<div class="tab" data-page="updatePage">Update</div>
<div class="tab" data-url="https://computerclub.hoogi.de/obpkeypad" data-window="help" id="helpButton">Help</div>
</div>
@@ -98,6 +100,15 @@ if (!window.isSecureContext) {
</div>
</div>
<div class="deviceForm tabPage hidden" id="devicePage">
<div class="buttons">
<button id="reloadDevices">Reload</button>
</div>
<div class="deviceFormRows">
</div>
</div>
<div class="tabPage hidden" id="updatePage">
<div class="row">
<span class="label">Firmware type</span>

View File

@@ -973,6 +973,14 @@
})
.catch(function (err) { alert("unable to load config: " + err) })
}
function loadDeviceList() {
getJson("api/devices")
.then(function(devices) {
let deviceForm = document.getElementById('devicePage');
console.log("Device form called");
})
.catch(function (err) { alert("unable to load devicelist: " + err) })
}
// New hash function by SubtleCrypto Browser API
async function hashString(str) {
const enc = new TextEncoder();
@@ -1089,6 +1097,9 @@
showOverlay(text, true);
});
}
buttonHandlers.reloadDevices=function() {
console.log("Button reload devices");
}
function handleTab(el) {
let activeName = el.getAttribute('data-page');
if (!activeName) {
@@ -1394,7 +1405,23 @@
}
}
}
// calculate MD5 hash of firmware to upload
let md5hash;
const fwreader = new FileReader();
fwreader.addEventListener('load', function (e) {
const content = new Uint8Array(e.target.result);
let binary = "";
const chunkSize = 0x8000;
for (let i = 0; i < content.length; i += chunkSize) {
binary += String.fromCharCode.apply(null, content.subarray(i, i + chunkSize));
}
md5hash = MD5(binary).toLowerCase();
});
fwreader.readAsArrayBuffer(file);
console.log("MD5:", md5hash);
let formData = new FormData();
formData.append("md5", md5hash)
formData.append("file1", el.files[0]);
req.open("POST", apiPrefix + '/api/update?_hash=' + encodeURIComponent(hash));
req.send(formData);

184
web/md5.js Normal file
View File

@@ -0,0 +1,184 @@
/**
*
* MD5 (Message-Digest Algorithm)
* http://www.webtoolkit.info/
*
**/
var MD5 = function (string) {
function RotateLeft(lValue, iShiftBits) {
return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits));
}
function AddUnsigned(lX,lY) {
var lX4,lY4,lX8,lY8,lResult;
lX8 = (lX & 0x80000000);
lY8 = (lY & 0x80000000);
lX4 = (lX & 0x40000000);
lY4 = (lY & 0x40000000);
lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);
if (lX4 & lY4) {
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
}
if (lX4 | lY4) {
if (lResult & 0x40000000) {
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
} else {
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
}
} else {
return (lResult ^ lX8 ^ lY8);
}
}
function F(x,y,z) { return (x & y) | ((~x) & z); }
function G(x,y,z) { return (x & z) | (y & (~z)); }
function H(x,y,z) { return (x ^ y ^ z); }
function I(x,y,z) { return (y ^ (x | (~z))); }
function FF(a,b,c,d,x,s,ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
};
function GG(a,b,c,d,x,s,ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
};
function HH(a,b,c,d,x,s,ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
};
function II(a,b,c,d,x,s,ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
};
function ConvertToWordArray(string) {
var lWordCount;
var lMessageLength = string.length;
var lNumberOfWords_temp1=lMessageLength + 8;
var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;
var lNumberOfWords = (lNumberOfWords_temp2+1)*16;
var lWordArray=Array(lNumberOfWords-1);
var lBytePosition = 0;
var lByteCount = 0;
while ( lByteCount < lMessageLength ) {
lWordCount = (lByteCount-(lByteCount % 4))/4;
lBytePosition = (lByteCount % 4)*8;
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<<lBytePosition));
lByteCount++;
}
lWordCount = (lByteCount-(lByteCount % 4))/4;
lBytePosition = (lByteCount % 4)*8;
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);
lWordArray[lNumberOfWords-2] = lMessageLength<<3;
lWordArray[lNumberOfWords-1] = lMessageLength>>>29;
return lWordArray;
};
function WordToHex(lValue) {
var WordToHexValue="",WordToHexValue_temp="",lByte,lCount;
for (lCount = 0;lCount<=3;lCount++) {
lByte = (lValue>>>(lCount*8)) & 255;
WordToHexValue_temp = "0" + lByte.toString(16);
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);
}
return WordToHexValue;
};
function Utf8Encode(string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
};
var x=Array();
var k,AA,BB,CC,DD,a,b,c,d;
var S11=7, S12=12, S13=17, S14=22;
var S21=5, S22=9 , S23=14, S24=20;
var S31=4, S32=11, S33=16, S34=23;
var S41=6, S42=10, S43=15, S44=21;
string = Utf8Encode(string);
x = ConvertToWordArray(string);
a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
for (k=0;k<x.length;k+=16) {
AA=a; BB=b; CC=c; DD=d;
a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);
d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);
c=FF(c,d,a,b,x[k+2], S13,0x242070DB);
b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);
a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);
d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);
c=FF(c,d,a,b,x[k+6], S13,0xA8304613);
b=FF(b,c,d,a,x[k+7], S14,0xFD469501);
a=FF(a,b,c,d,x[k+8], S11,0x698098D8);
d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);
c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);
b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);
a=FF(a,b,c,d,x[k+12],S11,0x6B901122);
d=FF(d,a,b,c,x[k+13],S12,0xFD987193);
c=FF(c,d,a,b,x[k+14],S13,0xA679438E);
b=FF(b,c,d,a,x[k+15],S14,0x49B40821);
a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);
d=GG(d,a,b,c,x[k+6], S22,0xC040B340);
c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);
b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);
a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);
d=GG(d,a,b,c,x[k+10],S22,0x2441453);
c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);
b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);
a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);
d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);
c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);
b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);
a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);
d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);
c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);
b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);
a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);
d=HH(d,a,b,c,x[k+8], S32,0x8771F681);
c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);
b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);
a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);
d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);
c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);
b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);
a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);
d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);
c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);
b=HH(b,c,d,a,x[k+6], S34,0x4881D05);
a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);
d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);
c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);
b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);
a=II(a,b,c,d,x[k+0], S41,0xF4292244);
d=II(d,a,b,c,x[k+7], S42,0x432AFF97);
c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);
b=II(b,c,d,a,x[k+5], S44,0xFC93A039);
a=II(a,b,c,d,x[k+12],S41,0x655B59C3);
d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);
c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);
b=II(b,c,d,a,x[k+1], S44,0x85845DD1);
a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);
d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);
c=II(c,d,a,b,x[k+6], S43,0xA3014314);
b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);
a=II(a,b,c,d,x[k+4], S41,0xF7537E82);
d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);
c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);
b=II(b,c,d,a,x[k+9], S44,0xEB86D391);
a=AddUnsigned(a,AA);
b=AddUnsigned(b,BB);
c=AddUnsigned(c,CC);
d=AddUnsigned(d,DD);
}
var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);
return temp.toLowerCase();
}